Install
openclaw skills install @thcjp/redis-context-cache-freeopenclaw skills install @thcjp/redis-context-cache-free键无TTL等于慢性OOM。GET后SET不是原子。KEYS * 会阻塞整个实例。这三条记住,省下80%的生产事故。
本技能为AI Agent提供Redis作为上下文缓存的最佳实践。Redis强大但陷阱密集:一个无TTL的键能让生产实例OOM,一个非原子的GET-SET能让分布式锁失效。本指南把这些陷阱整理成可直接复用的命令模板。
Redis使用者的三大痛点:
本技能的应对:
SET key value EX 3600,强制习惯┌─────────────────────────────────────────────────────────┐
│ Redis上下文缓存(免费版) │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 过期策略 │ │ 数据结构 │ │ 原子性 │ │
│ │ EXPIRE │ │ Sorted Set │ │ SETNX │ │
│ │ SETEX │ │ HyperLogLog │ │ WATCH/MULTI │ │
│ │ KEEPTTL │ │ Streams │ │ Lua │ │
│ │ 惰性删除 │ │ Hash │ │ INCR │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 内存管理 │ │ 常见模式 │ │ 常见陷阱 │ │
│ │ maxmemory │ │ cache-aside │ │ 无TTL泄漏 │ │
│ │ 驱逐策略 │ │ write-through│ │ KEYS阻塞 │ │
│ │ INFO memory │ │ 限流器 │ │ 大value │ │
│ │ 大key检测 │ │ 分布式锁 │ │ 阻塞操作 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
# 连接Redis
redis-cli
# 设置带TTL的缓存键(1小时过期)
SET user:session:abc123 "session_data" EX 3600
# 读取
GET user:session:abc123
# 查看剩余TTL
TTL user:session:abc123
# 查看内存使用
INFO memory | grep used_memory_human
实现cache-aside模式(最常用的缓存模式):
# 1. 查询缓存
GET user:profile:123
# 若返回 nil,则缓存未命中
# 2. 缓存未命中时,从DB查询后写入缓存
SET user:profile:123 '{"name":"Alice","email":"a@b.com"}' EX 3600
# 3. 数据更新时,更新缓存(带TTL)
SET user:profile:123 '{"name":"Alice Updated"}' EX 3600
# 4. 数据删除时,删除缓存
DEL user:profile:123
痛点:键无TTL会永远存在,导致内存持续增长直到OOM。
| 操作 | 命令 | 说明 |
|---|---|---|
| 设置带TTL的键 | SET key value EX 3600 | 3600秒后过期(推荐) |
| 单独设置TTL | EXPIRE key 3600 | 已存在的键加TTL |
| 设置过期时间点 | EXPIREAT key 1700000000 | Unix时间戳过期 |
| 查看剩余TTL | TTL key | -1=永不过期,-2=不存在 |
| 更新时保留TTL | SET key value KEEPTTL | Redis 6+,更新值不重置TTL |
| 取消TTL | PERSIST key | 改为永不过期 |
关键陷阱:
| 陷阱 | 说明 | 对策 |
|---|---|---|
| SET默认清除TTL | SET key value会移除原TTL | 用 SET key value KEEPTTL(Redis 6+) |
| 惰性删除 | 过期键访问时才删除,未访问会占内存 | 配合主动过期:SCAN + TTL 检查 |
| EXPIRE重置 | SET会重置TTL,但EXPIRE本身不重置 | 注意区分 |
输入: 用户提供功能1:过期策略(防止内存泄漏)所需的指令和必要参数。 处理: 按照skill规范执行功能1:过期策略(防止内存泄漏)操作,遵循单一意图原则。 输出: 返回功能1:过期策略(防止内存泄漏)的执行结果,包含操作状态和输出数据。
大多数用户只用String,错过Redis的强大数据结构:
# 滑动窗口限流:每用户每分钟100请求
ZADD limits:{user_id} {当前时间戳} {请求ID}
ZREMRANGEBYSCORE limits:{user_id} 0 {当前时间戳-60000}
ZCARD limits:{user_id} # 返回当前窗口内请求数
EXPIRE limits:{user_id} 120 # 2分钟后自动清理
优势:相比INCR固定窗口,滑动窗口更精确,无边界突刺。
# 统计UV(独立访客)
PFADD visitors:2026-07-18 {ip1} {ip2} {ip3}
PFCOUNT visitors:2026-07-18 # 返回近似UV数
优势:12KB存储数十亿独立值,误差0.81%。相比Set存IP,节省99%+内存。
# 生产者
XADD orders * order_id 12345 amount 99.9
# 消费者(阻塞读取)
XREAD BLOCK 5000 COUNT 10 STREAMS orders $
# 确认处理完成
XACK orders {consumer_group} {message_id}
优势:相比LIST,Streams支持消费者组、ACK确认、消息持久化,不丢消息。
# 存储用户对象(比JSON字符串更高效)
HSET user:1 name "Alice" email "a@b.com" age 30 login_count 0
# 原子性修改单个字段
HINCRBY user:1 login_count 1
# 读取单个字段
HGET user:1 email
优势:相比JSON String,Hash支持字段级原子更新,内存占用更小(ziplist编码)。
输入: 用户提供功能2:欠用的数据结构所需的指令和必要参数。 处理: 按照skill规范执行功能2:欠用的数据结构操作,遵循单一意图原则。 输出: 返回功能2:欠用的数据结构的执行结果,包含操作状态和输出数据。
痛点:GET后SET不是原子的,多客户端并发时会出错。
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| 计数器 | GET counter → SET counter {n+1} | INCR counter |
| 仅在不存在时设置 | GET key → if nil: SET key | SET key value NX EX 30 |
| 乐观锁 | GET key → 修改 → SET key | WATCH key → MULTI → SET key → EXEC |
| 复杂原子操作 | 多条命令 | Lua脚本:EVAL "script" keys args |
# 获取锁(30秒自动过期,防止死锁)
SET lock:resource {unique_token} NX EX 30
# 释放锁(必须验证token,防止误删别人的锁)
# 错误做法:直接 DEL lock:resource(可能删了别人的锁)
# 正确做法:用Lua脚本原子验证+删除
EVAL "
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
" 1 lock:resource {unique_token}
关键点:
# 监视key,若被修改则事务中止
WATCH counter
current = GET counter
MULTI
SET counter {current + 1}
EXEC # 若counter被其他客户端修改,返回nil,需重试
输入: 用户提供功能3:原子性陷阱所需的指令和必要参数。 处理: 按照skill规范执行功能3:原子性陷阱操作,遵循单一意图原则。 输出: 返回功能3:原子性陷阱的执行结果,包含操作状态和输出数据。
痛点:不设maxmemory的Redis会用光所有RAM,触发swap后性能崩塌。
# 设置maxmemory(生产必做)
CONFIG SET maxmemory 2gb
# 设置驱逐策略
# allkeys-lru: 纯缓存场景(推荐)
# volatile-lru: 混合场景(部分键持久化)
# noeviction: 持久化数据(写入超限报错)
CONFIG SET maxmemory-policy allkeys-lru
# 查看内存使用
INFO memory | grep -E "used_memory_human|maxmemory_human|evicted_keys"
| 策略 | 适用场景 | 说明 |
|---|---|---|
allkeys-lru | 纯缓存 | 所有键参与LRU驱逐,推荐 |
allkeys-lfu | 热点明显的缓存 | 按访问频率驱逐(Redis 4+) |
volatile-lru | 混合(部分持久化) | 仅驱逐带TTL的键 |
volatile-ttl | 优先驱逐快过期的 | 驱逐TTL最短的带TTL键 |
noeviction | 持久化数据 | 超限直接报错,不驱逐 |
# 检测大key(生产环境安全)
redis-cli --bigkeys
# 查看单个key的内存占用
MEMORY USAGE user:session:abc123
# 查看key的类型和大小
DEBUG OBJECT user:session:abc123 # 谨慎使用,生产环境避免
TYPE user:session:abc123
STRLEN user:session:abc123 # String类型
HLEN user:profile:123 # Hash类型
LLEN mylist # List类型
ZCARD myset # Sorted Set类型
大key危害:一个1GB的value驱逐时性能极差,应拆分为多个小key。
输入: 用户提供功能4:内存管理(生产关键)所需的指令和必要参数。 处理: 按照skill规范执行功能4:内存管理(生产关键)操作,遵循单一意图原则。 输出: 返回功能4:内存管理(生产关键)的执行结果,包含操作状态和输出数据。
# 读:先查缓存,未命中查DB后写缓存
GET user:123
# nil → 查DB → SET user:123 {db_data} EX 3600
# 写:更新DB,删除缓存(而非更新缓存)
# DB.update(user, data)
DEL user:123
为什么写时删除而非更新? 避免并发写入导致缓存与DB不一致。
# 写:同时写DB和缓存
# DB.insert(user)
SET user:123 {data} EX 3600
# 读:只查缓存
GET user:123
适用:写少读多,且能接受短暂的写延迟。
# 每IP每分钟100请求
INCR rate:{ip}:{minute}
# 若返回1,说明是新窗口
EXPIRE rate:{ip}:{minute} 60
# 检查是否超限
# count = GET rate:{ip}:{minute}
# if count > 100: 拒绝
缺点:窗口边界突刺(第59秒和第01秒各100请求=2秒内200请求)。专业版提供滑动窗口方案。
# 获取锁
SET lock:order:{order_id} {token} NX EX 30
# 执行业务逻辑
# ...
# 释放锁(Lua脚本,见功能3)
输入: 用户提供功能5:常见缓存模式所需的指令和必要参数。 处理: 按照skill规范执行功能5:常见缓存模式操作,遵循单一意图原则。 输出: 返回功能5:常见缓存模式的执行结果,包含操作状态和输出数据。 能力覆盖范围:本skill的核心能力覆盖以下场景关键词:Agent、上下文缓存实践指、覆盖过期策略、内存管理与常见模、上下文缓存、免费版、作为上下文缓存的、最佳实践指南、非原子、等真实生产痛点、提供可直接复用的、命令模板与陷阱清、when、模型调用、智能对话、LLM、应用时使用、不适用于需要、确定性的关键决策等。这些关键词对应description中声明的使用场景,均已在上述能力点中提供对应的操作支持。
input_params参数,支持创建/查询/导出操作需求:AI Agent需要缓存用户会话上下文,避免每次都重新加载。
# 缓存会话上下文(30分钟过期)
SET agent:session:{user_id} '{"messages":[...],"context":"..."}' EX 1800
# 更新上下文(保留原TTL)
SET agent:session:{user_id} '{"messages":[...new...]}" KEEPTTL
# 会话结束时清除
DEL agent:session:{user_id}
陷阱:不设TTL会导致会话状态永远残留,内存持续增长。
需求:限制每用户每分钟最多100次API调用。
# 使用Sorted Set实现滑动窗口
ZADD ratelimit:{user_id} {now_ms} {request_uuid}
ZREMRANGEBYSCORE ratelimit:{user_id} 0 {now_ms - 60000}
count = ZCARD ratelimit:{user_id}
if count > 100:
reject
else:
allow
EXPIRE ratelimit:{user_id} 120
优势:相比INCR固定窗口,滑动窗口无边界突刺问题。
需求:商品详情页QPS 1万+,DB无法承受,需Redis缓存。
# 缓存商品详情(1小时过期)
SET product:{product_id} '{"name":"...","price":99.9}' EX 3600
# 缓存未命中时的回源
GET product:123
# nil → 查DB → SET product:123 {db_data} EX 3600
# 防止缓存雪崩:TTL加随机抖动
SET product:123 {data} EX $((3600 + RANDOM % 600)) # 3600-4200秒
陷阱:所有键同一时刻过期会导致缓存雪崩,TTL加随机抖动可缓解。
| 陷阱 | 后果 | 对策 |
|---|---|---|
| 键无TTL | OOM、内存泄漏 | 每个SET必带EX |
| GET后SET | 并发覆盖、数据丢失 | 用INCR/SETNX/Lua |
| KEYS * | 阻塞整个实例数秒 | 用SCAN替代 |
| 大value | 驱逐性能差、网络阻塞 | 拆分为小key,单value<10KB |
| 不设maxmemory | 用光RAM、触发swap | 生产必设maxmemory |
| 用作主数据库无持久化 | 重启数据丢失 | 启用AOF或RDB |
| 阻塞操作 | 单线程卡死 | 避免KEYS/大Lua/大SORT |
| SETNX锁不验证token | 误删别人的锁 | Lua脚本验证+删除 |
| Pub/Sub不持久化 | 离线订阅者丢消息 | 用Streams替代 |
| 跨槽位MGET | Cluster报错 | 用Hash Tag确保同槽 |
A: 不推荐。Redis是内存数据库,默认持久化(RDB/AOF)有窗口期,重启可能丢数据。推荐作为缓存/会话/限流等辅助存储,主数据放PostgreSQL/MySQL。如果必须用Redis做主库,启用AOF + appendfsync always(每次写入都fsync,性能下降明显)。
A: 取决于业务。会话状态30分钟-2小时,商品缓存1-24小时,配置缓存5-30分钟。原则:能短不长(避免脏数据),但加随机抖动(避免雪崩)。如 EX $((3600 + RANDOM % 600))。
A: Redis是单线程的,KEYS *会遍历所有键,期间无法处理其他命令。10万键的实例KEYS *可能阻塞2-3秒,生产环境这是不可接受的。用SCAN替代:SCAN 0 MATCH user:* COUNT 100,分批迭代不阻塞。
A: 不够。SETNX获取锁后,如果持有者崩溃,锁会永久存在(死锁)。必须加TTL:SET lock NX EX 30。释放锁时不能直接DEL(可能删了别人的锁),要用Lua脚本验证token后删除。专业版提供Redlock算法与锁续期方案。
A: 都用。RDB做定期快照(恢复快),AOF做增量日志(丢数据少)。配置:save 900 1 + appendfsync everysec。everysec是性能与安全的平衡点,最多丢1秒数据。专业版提供详细的持久化调优指南。
| 依赖项 | 类型 | 是否必需 | 获取方式 |
|---|---|---|---|
| LLM API | API | 必需 | 由Agent平台内置LLM提供 |
| Redis | 服务 | 必需 | 本地安装或云服务(Redis Cloud/阿里云Redis) |
| redis-cli | 工具 | 必需 | 随Redis安装 |
本技能基于原始开源作品改进,保留原始版权声明:
本改进作品在原始作品基础上进行了深度差异化改造,包括但不限于:
原始MIT license允许使用、复制、修改和分发,需保留版权声明。本改进作品在保留原始版权声明的基础上添加自有署名,完全符合MIT license要求。
本免费体验版限制以下高级功能:
解锁全部功能请使用专业版:redis-context-cache-pro
### 60秒上手:第一个缓存键
```bash
| 错误场景 | 原因 | 处理方式 |
|---|---|---|
| 配置错误 | 参数缺失或格式错误 | 检查依赖说明中的配置要求 |
| 运行时错误 | 运行环境不满足 | 确认运行环境符合依赖说明 |
| 网络错误 | 连接超时或不可达 | 执行ping命令测试网络连通性,检查防火墙和代理设置连接后执行ping命令测试网络连通性,检查防火墙和代理设置连接后重新执行命令,参考国内替代方案 |