Redis 常用命令与实战手册
这份手册围绕日常开发和排障中最常见的需求:缓存、计数、对象字段、去重、列表、排行榜、限流和问题定位。“覆盖 80%”是选材目标,不是统计结论;真正能用于工作,需要把命令、数据类型、过期时间和并发边界一起记住。
适用范围:示例采用 Redis Open Source 6.2 起支持的语法,兼顾后续版本;这是语法基线,不是部署版本建议。新版本特性另行注明。官方文档核对日期:2026-09-17。
示例约定:bash 代码在操作系统终端执行,text 中的命令在 redis-cli 交互界面执行;Lua 代码需通过 EVAL 或客户端脚本接口调用。示例使用测试环境的 demo: 前缀,各组独立操作示例键。返回值说明假设这些键没有旧数据,也没有其他客户端修改;除明确标注的集群示例外,按单机数据库 0 演示。
想直接动手操作:先看第 15 节的完整案例,按“业务需求 → 执行命令 → 对照结果 → 理解边界”学习,再回到前面的命令表查参数。
1. 优先记住这张表
先掌握“第一优先级”,覆盖常见业务读写;第二轮再学习组合操作和排障。后面的 Stream、位图、地理位置按业务需要查阅。
| 优先级 | 工作需求 | 优先记住的命令 | 一句话记忆 |
|---|---|---|---|
| 第一 | 缓存一个值或一份 JSON | SET、GET、MGET | 写缓存通常把 EX 一起带上 |
| 第一 | 判断、查类型、删除 | EXISTS、TYPE、DEL、UNLINK | 不知道类型先 TYPE,大键优先 UNLINK |
| 第一 | 控制生命周期 | EXPIRE、TTL、PTTL | 秒、毫秒要分清;-1 没过期时间,-2 不存在 |
| 第一 | 并发计数 | INCR、INCRBY、DECR | 让 Redis 加减,不在客户端“先读再写” |
| 第一 | 修改对象的部分字段 | HSET、HGET、HMGET、HDEL、HINCRBY | Hash 是一个键里 面的字段表 |
| 第一 | 去重、点赞、是否加入 | SADD、SREM、SISMEMBER、SCARD | Set 成员唯一,顺序不保证 |
| 第一 | 最近记录、简单队列 | LPUSH、RPUSH、LPOP、LRANGE、LTRIM、LLEN | 同端进出是栈,异端进出是队列 |
| 第一 | 排行榜、按分值查询 | ZADD、ZINCRBY、ZRANGE、ZREVRANK、ZSCORE、ZREM | ZSet 的成员唯一,按分数排序 |
| 第二 | 按前缀找键 | SCAN、HSCAN、SSCAN、ZSCAN | 用游标分批查,不在生产库全量 KEYS |
| 第二 | 查慢、查内存、查复制 | INFO、SLOWLOG GET、MEMORY USAGE | 先拿证据,再改配置 |
| 第二 | 多步并发操作 | MULTI、EXEC、WATCH、EVAL | Pipeline 减少往返,事务与脚本处理执行边界 |
选数据结构时,可以先问自己:整份读写用 String;字段单独更新用 Hash;只关心有无用 Set;关心插入顺序用 List;关心分数或时间顺序用 ZSet。
2. 连接、命名与返回值
2.1 连接到正确的实例
本地测试实例:
redis-cli -h 127.0.0.1 -p 6379 -n 0
需要用户名、密码和 TLS 的环境,替换下面的地址、用户名和证书路径。--askpass 交互输入密码,避免把密码写进命令历史;TLS 用于加密连接。
redis-cli -h redis.example.com -p 6379 --user app --askpass --tls --cacert /path/to/ca.pem
进入交互界面后:
PING
INFO server
ACL WHOAMI
SELECT 0
PING 通常返回 PONG;INFO server 的 redis_version 是服务端版本,redis-cli --version 只显示客户端版本。SELECT 切换当前连接的逻辑数据库;Redis Cluster 只支持数据库 0。逻辑数据库共用实例资源,不能代替环境和权限隔离。连接参数见 Redis CLI 官方文档,集群数据库限制见 Cluster 文档。
2.2 键名和类型
建议键名表达环境、应用、业务和对象,例如 prod:shop:cache:user:42。冒号只是命名约定,Redis 不会因此建立目录。不要把所有用户塞进一个无限增长的 Hash 或 Set。
一个键对应一个值及其数据类型。demo:cache:user:42 可以存 JSON 字符串;demo:user:42 可以存 Hash,二者是两个不同的键。对 Hash 执行 GET 通常会报 WRONGTYPE,应使用 HGET 或 HMGET。
2.3 不要把所有返回值都当成“成功或失败”
| 返回值 | 常见含义 |
|---|---|
OK | 命令执行成功,例如普通 SET |
(nil) / 客户端的空值 | 键不存在,或 SET ... NX 条件不满足;依命令判断 |
整数 0、1 或更大 | 可能是新增数、删除数、成员数量或布尔结果 |
| 空数组 | 查询没有匹配的元素 |
WRONGTYPE | 使用了不适合当前值类型的命令 |
例如,HSET 更新一个已有字段可能返回 0,意思是没有新增字段,更新仍然成功。客户端对返回值的包装不同,要看对应方法的约定。
3. 通用键命令:查找、过期、删除
| 命令示例 | 用途和结果 |
|---|---|
TYPE demo:user:42 | 查类型,例如 hash;不存在为 none |
EXISTS demo:user:42 | 单键存在返回 1,不存在返回 0;多键返回存在数量 |
EXPIRE demo:user:42 600 | 从现在起 600 秒过期;成功设置返回 1 |
PEXPIRE demo:user:42 1500 | 从现在起 1500 毫秒过期 |
TTL demo:user:42 | 剩余秒数;非负数表示剩余时间 |
PTTL demo:user:42 | 剩余毫秒数;需要更细粒度时使用 |
PERSIST demo:user:42 | 移除过期时间,不是“保存到磁盘” |
DEL demo:user:42 | 删除键,返回实际删除数量 |
UNLINK demo:user:42 | 从键空间移除,内存回收交给后台处理 |
SCAN 0 MATCH demo:user:* COUNT 100 | 从游标 0 开始,分批查找匹配键 |
3.1 过期时间最容易出错的地方
TTL / PTTL 返回 -1 表示键存在但没有过期时间,返回 -2 表示键不存在。TTL 返回 0 也属于非负剩余时间,不代表“永不过期”。见 TTL。
SET demo:ttl "v1" EX 60
TTL demo:ttl
SET demo:ttl "v2"
TTL demo:ttl
第一次 TTL 接近 60;普通 SET 覆盖后,第二次返回 -1。需要保留原过期时间时使用 SET demo:ttl "v2" KEEPTTL,需要重新计时时使用 SET demo:ttl "v2" EX 60。KEEPTTL 不会为原本没有 TTL 的键补上 TTL。见 SET。
INCR、HSET、LPUSH 等原地修改已有键的命令,保留该键已有的过期时间;新建出来的键不会自动带 TTL。EXPIRE 再次执行会重设剩余时间,非正数过期时间会使键被删除。Redis 通过访问时检查和后台周期检查清理过期键,因此不能把 TTL 当成准点执行任务的定时器。见 EXPIRE。
3.2 SCAN 的正确循环
SCAN 0 MATCH demo:user:* COUNT 100
结果分两部分:下一次游标、这一批键。把返回的游标用于下一次请求,直到返回游标为 0。例如本次返回 37,下一次才是 SCAN 37 MATCH demo:user:* COUNT 100。
COUNT 100是工作量提示,不能保证恰好返回 100 个键。- 本批为空也不能停止,是否结束只看游标。
- 可能返回重复键;边遍历边修改时,结果不是某个时刻的完整快照。
MATCH不是前缀索引,一次完整遍历仍可能扫描整个键空间。HSCAN、SSCAN、ZSCAN用来遍历单个集合,遵守相同的游标规则。- Cluster 下需要遍历各主节点;
redis-cli -c --scan不会自动扫完整个集群。
在终端快速查找本地测试实例的键,可以使用:
redis-cli -h 127.0.0.1 -p 6379 -n 0 --scan --pattern 'demo:user:*'
批量清理按“确认前缀 → 分批扫描 → 小批删除 → 限速和核对”处理。并发业务可能重新创建同名键,扫描后再删存在时间差;需要严格区分新旧数据时,应使用缓存版本前缀等机制。不要直接把扫描结果通过 xargs 全量删除,键名含空格、换行及集群跨槽时也容易出错。见 SCAN 和 UNLINK。
4. String:缓存、计数、短期占位
4.1 常用命令
| 命令示例 | 解决什么需求 |
|---|---|
SET demo:cache:user:42 '{"name":"小王"}' EX 300 | 整份缓存,有效期 5 分钟 |
GET demo:cache:user:42 | 读取字符串;不存在返回空值 |
MGET demo:cache:user:42 demo:cache:user:43 | 一次读多个字符串,结果与键顺序对应 |
MSET demo:cfg:a "on" demo:cfg:b "off" | 一次设置多个值;不支持同时指定 TTL |
SET demo:guard:job:42 "request-token" NX EX 30 | 键不存在时才创建,并同时设置过期时间 |
SET demo:cfg:a "off" XX | 只覆盖已经存在的键 |
INCR demo:views:42 | 加 1;键不存在时从 0 开始 |
INCRBY demo:views:42 10 | 加指定整数,返回更新后的值 |
DECR demo:stock:42 | 减 1;不会自动阻止减到负数 |
GETEX demo:session:abc EX 1800 | 读字符串并重新设置 30 分钟有效期 |
GETDEL demo:temp-result:42 | 原子地取出字符串并删除 |
STRLEN demo:cache:user:42 | 字符串字节数,不是中文字符数 |
SET ... NX 成功返回 OK,键已存在则返回空值。不要用 EXISTS 再 SET 代替它,两个请求之间可能被其他客户端插入。NX 只判断是否存在,不比较旧值;XX 也不是“旧值相等才更 新”。
需要缓存过期时间时,优先 SET ... EX/PX,不要拆成 SET 和 EXPIRE 两次调用,中间崩溃可能留下永久键。MSET 覆盖也不会保留原 TTL;批量写带 TTL 的缓存可用客户端 Pipeline 发送多条 SET ... EX。批量覆盖的语义见 MSET。
MGET 对不存在的键以及非 String 键都返回对应位置的空值,排查时不能只据此断言数据不存在。相关语义见 String、MGET 和 SET。
4.2 计数与读取后删除的边界
并发计数不要执行“GET → 本地加 1 → SET”。两个请求可能同时读到 10,最后都写入 11。INCR 直接由 Redis 完成加法;值必须是整数表示,且不能超出有符号 64 位整数范围。需要精确金额时,优先存整数分并使用整数加减,避免用浮点数累计金额。见 INCR。
GETEX 适合滑动续期的会话,但不适合任何缓存都“读一次续一次”,否则热点旧数据可能持续不刷新。会话如果还有绝对有效期,要在应用里另行 判断。见 GETEX。
GETDEL 适合“领取后移除”的临时结果。它不负责校验用户输入:验证码如果要求“输错还能重试”,不能先 GETDEL 再比较;应在一个短脚本内比较、成功后删除,并限制尝试次数。见 GETDEL。
5. Hash:对象字段、购物车
Hash 可以理解为一个键下的字段表,例如一个用户的 name、city、login_count,字段值仍由应用编码、解码。
HSET demo:user:42 name "小王" city "上海" login_count 0
HGET demo:user:42 name
HMGET demo:user:42 name city
HINCRBY demo:user:42 login_count 1
HEXISTS demo:user:42 city
HLEN demo:user:42
HDEL demo:user:42 city
EXPIRE demo:user:42 600
依次可以得到姓名、两个字段值、递增后的登录次数、字段是否存在、字段数量。最后的 EXPIRE 设置整个 Hash 键的有效期。
| 需求 | 命令 | 注意点 |
|---|---|---|
| 查看一个小对象所有字段 | HGETALL demo:user:42 | 返回全部字段和值,只用于大小可控的对象 |
| 遍历较大的 Hash | HSCAN demo:user:42 0 COUNT 100 | 按游标继续,不是固定页码 |
| 购物车增加某商品数量 | HINCRBY demo:cart:42 sku:1001 1 | 数量减到 0 时是否删字段,由业务决定 |
| 多字段写入 | HSET key field1 value1 field2 value2 | 新代码用 HSET,旧 HMSET 已弃用 |
String JSON 适合整份读取、整份替换;Hash 适合局部字段读写。不要为了使用 Hash,把所有用户都放进 users 一个键,导致大键、过期粒度和集群分布问题。
HSET 与 EXPIRE 分开发送也有中间失败窗口;新建 Hash 且必须带 TTL 时,在参数与类型正确的前提下,用同一事务或短脚本组合。Redis 6.2 的 Hash 只能设置整个键的 TTL;Redis 7.4 起提供 HEXPIRE 等字段过期命令,使用前检查实际服务端和客户端支持。
参考:Hash、HMSET 弃用说明、HEXPIRE 版本说明。
6. List:最近记录与简单队列
List 保留插入顺序,允许重复值;它不是按分数排序的集合。
6.1 最近浏览记录
LPUSH demo:recent:42 item:1001
LPUSH demo:recent:42 item:1002
LRANGE demo:recent:42 0 9
LTRIM demo:recent:42 0 99
LLEN demo:recent:42
LRANGE ... 0 9 读取前 10 项,示例顺序为 item:1002、item:1001。LTRIM ... 0 99 只保留最近 100 项。索引从 0 开始,区间两 端都包含,-1 表示最后一项。
重复浏览会重复插入;要按商品去重并按最后访问时间排序,可考虑 ZSet。并发写入时若需要把“插入和截断”作为一个操作执行,可放在同一事务内。
6.2 先进先出的简单队列
RPUSH demo:queue:email task:1 task:2
LPOP demo:queue:email
BLPOP demo:queue:email 5
RPUSH + LPOP 先取出 task:1,再取出 task:2。BLPOP 在没有数据时等待,最多 5 秒;阻塞的是等待的客户端连接,Redis 仍可服务其他客户端。阻塞消费宜使用专用连接。
弹出成功后,任务已经从 List 删除。如果消费者此时崩溃,任务可能丢失。需要确认和重试时,考虑 Stream,或用 LMOVE / BLMOVE 转入处理中列表并自行维护确认、超时回收;普通 List 队列没有自动确认机制。见 List 官方说明。
7. Set:去重、点赞、集合关系
SADD demo:likes:article:1001 user:42 user:43
SADD demo:likes:article:1001 user:42
SISMEMBER demo:likes:article:1001 user:42
SCARD demo:likes:article:1001
SREM demo:likes:article:1001 user:42
第一次 SADD 返回新增的 2 个成员;重复添加 user:42 返回 0。取消点赞前,SISMEMBER 返回 1,SCARD 返回 2。只要总数可直接用 SCARD 获取,就不必额外维护一个容易不同步的计数键。
| 需求 | 命令示例 |
|---|---|
| 查看小集合 | SMEMBERS demo:likes:article:1001 |
| 遍历大集合 | SSCAN demo:likes:article:1001 0 COUNT 100 |
| 共同关注 | SINTER demo:follow:42 demo:follow:43 |
| 合并去重 | SUNION demo:follow:42 demo:follow:43 |
| 42 关注了、43 没关注 | SDIFF demo:follow:42 demo:follow:43 |
| 随机抽取但保留成员 | SRANDMEMBER demo:candidates 2 |
| 随机抽取并移除成员 | SPOP demo:candidates 2 |
SDIFF 的顺序有意义,反过来就是另一个结果。交并差集可能遍历大量成员,不能认为一次命令就一定便宜;多键集合运算在 Cluster 中也受同槽限制。资料:Set。
8. ZSet:排行榜和时间范围查询
ZSet 保存唯一成员 member 和对应分数 score。成员不重复,分数允许重复;默认按分数升序,同分按成员的字节字典序排列,并非按插入先后。
8.1 排行榜
ZADD demo:rank:weekly 100 user:42 80 user:43 120 user:44
ZINCRBY demo:rank:weekly 30 user:43
ZRANGE demo:rank:weekly 0 9 REV WITHSCORES
ZREVRANK demo:rank:weekly user:43
ZSCORE demo:rank:weekly user:43
ZCARD demo:rank:weekly
此时分数为 user:44 = 120、user:43 = 110、user:42 = 100。ZRANGE ... REV 获取降序榜单;ZREVRANK 返回从 0 开始的名次,展示给用户时再加 1。成员不存在时名次为空值,不要显示为第 1 名。
8.2 按分数查询与删除
ZRANGE demo:rank:weekly 100 120 BYSCORE WITHSCORES
ZRANGE demo:rank:weekly +inf 100 BYSCORE REV LIMIT 0 10 WITHSCORES
ZCOUNT demo:rank:weekly 100 120
ZREM demo:rank:weekly user:42
ZREMRANGEBYSCORE demo:rank:weekly -inf 80
第一条按分数查询闭区间 [100, 120];加 REV 时,分数范围的参数也要按“最大值、最小值”填写。上界、下界可用 +inf、-inf;用 "(100" 表示排除 100。
ZRANGE key 0 9 是按名次取区间,加 BYSCORE 后才按分数查询。大偏移量的 LIMIT 仍有遍历成本,持续变化的榜单也不提供分页快照。
Redis 6.2 起可以用 ZRANGE ... REV/BYSCORE 表达常用的降序和按分数查询;维护旧代码时还会见到 ZREVRANGE、ZRANGEBYSCORE。见 ZRANGE。
把分数换成时间戳,也能找出“截止某时刻到期的任务”。但“查到任务 → 删除任务”分两次执行会被并发消费者重复领取;领取要原子完成,处理失败还需要确认与重试设计。不要把一个 ZSet 查询等同于完整可靠的延迟队列。
9. 多步操作:Pipeline、事务、Lua 怎么选
原子执行在这里指操作执行过程中,其他客户端不能插进来修改相关数据;它不自动代表出错回滚、已经落盘,或已经复制到从节点。
| 方式 | 适合解决什么问题 | 不能指望它解决什么 |
|---|---|---|
| 单条命令 | INCR 计数、SADD 去重、SET ... NX EX 占位 | 不能把随后访问数据库的操作一起包住 |
| Pipeline | 客户端连续发送一批命令,减少网络往返 | 不保证整批原子执行,也不自动回滚 |
MULTI / EXEC | 多条已确定命令连续执行,中途不穿插其他客户端命令 | 不支持根据中间返回值在客户端临时分支,不提供回滚 |
WATCH + 事务 | 先读再计算;提交前检查读过的键是否被修改 | 冲突后需重新读取、计算并有限重试 |
Lua / EVAL | “读取 → 判断 → 更新”在服务端一次完成 | 长脚本会阻塞命令执行,运行时错误不会自动撤销已做的写入 |
Pipeline 通常是客户端 API,不存在一条通用的 PIPELINE Redis 命令。批量大小要同时限制条数和字节数,逐条检查返回结果。更多见 Pipeline 和 Lua 脚本。
9.1 两条固定命令一起提交
MULTI
LPUSH demo:recent:42 item:1003
LTRIM demo:recent:42 0 99
EXEC
EXEC 前的命令先进入队列,通常返回 QUEUED;EXEC 后逐条返回结果。未执行时可用 DISCARD 放弃队列。
- 命令入队阶段发现语法等错误,事务会被标记失败,
EXEC不执行这批命令。 - 执行阶段某条命令因类型不符等原因失败,其他命令仍可能执行,已经成功的操作不回滚。
WATCH要放在读取之前,在同一条连接完成WATCH → 读取 → MULTI → 写入 → EXEC。被监视键期间发生修改,包括过期,EXEC可返回空结果;这表示冲突未提交,应重新读后重试。- 放弃业务操作时用
UNWATCH清理监视状态;不能把带监视状态的连接直接当成干净连接交回连接池。
事务具体保证见 Transactions。
9.2 一个计数与过期必须一起完成的限流例子
假设规则是:一个用户从本窗口首次请求开始,60 秒内允许 100 次请求;拒绝的请求也记入计数。键只由这段逻辑维护,不被其他代码重置或减计数。
EVAL "local n = redis.call('INCR', KEYS[1]); if n == 1 then redis.call('EXPIRE', KEYS[1], 60) end; return n" 1 demo:rate:api:user:42
应用读取返回计数:不超过 100 放行,超过 100 拒绝。EVAL 中的 1 表示后面有 1 个键参数,脚本通过 KEYS[1] 访问它。
这段脚本把“首次计数”和“设置 TTL”放在一起,避免客户端在两条命令中间崩溃,留下永久计数键。只在新窗口第一次请求设置过期时间,否则每次续期会改变限流规则。此方案是固定窗口,有窗口交界处的突发问题,不能保证任意连续 60 秒都不超过 100 次;更严格的需求再考虑滑动窗口或令牌桶。算法思路参考 INCR 的限流示例。
脚本只做短小、可控的逻辑。把键放进 KEYS,普通参数放进 ARGV;不要用拼接用户输入的方式生成 Lua 代码。动态参数和类型要尽量在写入前校验,避免脚本中途报错留下部分修改。高频脚本可由客户端使用 SCRIPT LOAD / EVALSHA,并处理脚本缓存不存在时的重新加载。见 Lua 脚本。
10. 把命令用于实际业务
10.1 缓存查询与更新
常用的 Cache Aside,也叫旁路缓存,流程是:
读取:GET 缓存 → 命中则返回 → 未命中查数据库 → SET 缓存并设置 TTL
更新:先提交数据库事务 → 成功后删除对应缓存 → 后续读取重新加载
数据库是权威数据源,TTL 根据业务允许的陈旧时间选择。更新数据库后删除缓存,是便于维护的常见起点;相关模式见 Redis Cache Aside。
它仍有并发窗口:请求 A 查到旧数据后暂停;请求 B 更新数据库并删除缓存;A 恢复后又把旧数据写回缓存。删除失败也可能留下旧值。因此,这个流程加上 TTL 并不等于强一致。
按业务要求补强:缓存删除失败要有可追踪的重试;更严格的场景使用数据版本校验、数据库变更事件驱动失效,或直接从数据库读取关键结果。不能用“Redis 很快”或固定延迟再删一次,代替并发一致性设计。
10.2 穿透、击穿、雪崩怎么区分
| 问题 | 具体例子 | 常用处理 |
|---|---|---|
| 缓存穿透 | 持续请求数据库也没有的用户 ID | 校验输入;短时间缓存“没有该记录”的明确标记;按需增加布隆过滤器 |
| 缓存击穿 | 一个热门商品缓存刚过期,大量请求同时查库 | 合并同键加载请求,或短期互斥重建;等待方有超时与降级策略 |
| 缓存雪崩 | 大量缓存同一时间过期,或 Redis 整体不可用 | TTL 加随机偏移;限制回源并发;预热与降级,保护数据库 |
“空值缓存”要能区分没缓存与已确认不存在,且设置较短 TTL;新增记录后也要清除对应的“不存在”缓存。布隆过滤器是一种节省内存的集合判断结构,可能把不存在的值判断为可能存在,维护规则要与数据新增保持一致。
热门键加载合并的做法可参考 官方缓存示例。上表是应用方案选择,不是 Redis 自动提供的完整保护机制。
10.3 短期互斥锁:会写加锁,也要会安全解锁
加锁:
SET demo:lock:rebuild:user:42 "unique-request-token" NX PX 10000
实际程序每次获取锁都生成新的随机 token,不能照抄示例的固定字符串。只有拿到 OK 的请求才开始受保护的工作。锁最长 10 秒;超时或返回不明确时,不能假定已经安全持有锁。
释放时,比较 token 与删除必须在同一个原子操作内:
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end; return 0" 1 demo:lock:rebuild:user:42 "unique-request-token"
假设 A 的锁已过期,B 重新拿到锁;A 工作结束后若直接 DEL,会删掉 B 的锁。比较 token 能防止这种误删,但不能阻止过期后的 A 继续执 行业务。
还要考虑任务超时、续期失败、进程暂停和主从切换时的锁丢失。涉及扣款、库存等正确性要求时,需由数据库条件更新、唯一约束,或受保护资源支持的递增令牌校验阻止旧请求继续写入;只靠这个锁示例不够。以上限制见 Redis 分布式锁文档。
10.4 防重复请求与超时重试
SET ... NX EX 可以在一段时间内抢占一个请求 ID,但抢占成功后进程仍可能崩溃。它不能独自证明业务已经成功,更不能保证订单永不重复创建。
涉及持久业务时,记录“处理中 / 已成功 / 失败”等状态及结果,并用数据库唯一约束等机制兜底。这里的幂等指同一个业务请求重试,不重复产生业务效果。
客户端报超时也不代表 Redis 没执行。例如 INCR 已执行但响应丢失,盲目重试会再加一次;普通 SET 重试虽然写入相同值,也可能重新计算 TTL。重试策略需要区分命令和业务语义。
11. 排障:从现象找到要看的命令
11.1 第一轮检查
以下命令用于读取状态;托管环境或受限账号可能没有部分权限。
PING
INFO server
INFO clients
INFO memory
INFO stats
INFO commandstats
INFO replication
INFO persistence
INFO keyspace
SLOWLOG GET 10
| 现象 | 先查什么 | 如何理解证据 |
|---|---|---|
| 键“突然没了” | 实例地址、数据库号、TYPE、TTL、INFO stats | 检查过期、业务删除、内存淘汰,也检查是否连错环境 |
写入提示 OOM | INFO memory、CONFIG GET maxmemory、CONFIG GET maxmemory-policy | 看内存上限和淘汰策略,不能只靠增加重试 |
| 接口变慢 | SLOWLOG GET 10、INFO commandstats | 看命令执行耗时及调用量,再查网络与应用连接池等待 |
| 连接数异常 | INFO clients、按需 CLIENT LIST | 看连接泄漏、阻塞连接、连接池配置 |
读到旧数据、写报 READONLY | INFO replication、ROLE | 确认主从角色、读路由及复制状态 |
| 重启后数据缺失 | INFO persistence、持久化配置、服务日志 | 确认是否开启持久化,以及最近保存是否成功 |
命令报 CROSSSLOT | CLUSTER KEYSLOT key | 多键操作涉及不同槽位,不能用重试解决 |
命令报 MOVED | 客户端的 Cluster 配置 | 服务端要求访问正确节点,应用要使用集群客户端 |
报 NOAUTH / NOPERM | 认证参数、ACL WHOAMI | 分别排查未认证及账号权限不足 |
INFO stats 中,expired_keys 是过期键累计数量,evicted_keys 是因内存限制淘汰的累计数量;它们不能单独证明某个具体键的删除原因。keyspace_hits / keyspace_misses 是键查找统计,观察时间段内的增量通常比历史总数更有用,也不能直接替代应用层缓存命中率。字段含义见 INFO。
SLOWLOG 记录服务端执行命令的耗时,单位是微秒,不包含客户端网络往返和连接池等待;没有慢日志不代表接口一定快。见 SLOWLOG GET。
LATENCY LATEST / LATENCY DOCTOR 可进一步辅助排查,但需要先配置非零的 latency-monitor-threshold 开启采样,阈值单位为毫秒;无记录不能排除问题。见 延迟监控。
11.2 大键和热键
大键是单个键的值很大或成员很多;热键是访问过于集中。一个很短的字符串也可能是热键,二者处理方向不同。
定位已知键:
TYPE demo:user:42
MEMORY USAGE demo:user:42
HLEN demo:user:42
根据类型换用 STRLEN、LLEN、SCARD、ZCARD,不要把上述命令都对同一个键执行。MEMORY USAGE 返回字节数;集合默认通过采样估算,SAMPLES 0 全量取样可能更贵。见 MEMORY USAGE。
需要扫描测试实例寻找大键时:
redis-cli -h 127.0.0.1 -p 6379 -n 0 --bigkeys -i 0.1
redis-cli -h 127.0.0.1 -p 6379 -n 0 --memkeys -i 0.1
--bigkeys 按类型报告字符串长度或集合成员数,--memkeys 看内存占用。它们会扫描键空间,生产环境应低峰、限速并观察影响,Cluster 要覆盖各节点。--hotkeys 依赖 LFU 淘汰策略下的访问频次计数,不是所有实例都可直接使用;也可从应用监控识别热点请求。工具说明见 Redis CLI。
大键通常通过限制集合长度、拆分和批量访问来处理,删除优先考虑 UNLINK。热键可考虑合并请求、本地缓存或读取副本,同时明确数据一致性要求;单纯增加 Cluster 节点不会自动拆开一个热键。
11.3 这些操作先想清楚再执行
| 操作 | 主要影响 | 常见替代或使用边界 |
|---|---|---|
KEYS * | 一次遍历整个键空间,拖慢其他请求 | 用 SCAN 分批遍历 |
大集合的 HGETALL / SMEMBERS / LRANGE 0 -1 | 大量计算、内存和网络响应 | 指定字段、限制范围,或使用扫描命令 |
大键 DEL | 回收复杂对象可能长时间占用主线程 | 优先 UNLINK,并控制批量大小 |
MONITOR | 持续输出命令流,增加开销,也会暴露业务参数 | 优先慢日志和指标,确需使用时限定时间 |
SAVE | 同步生成快照,阻塞服务 | 按持久化方案使用后台快照,仍要评估资源 |
FLUSHDB / FLUSHALL | 清空当前数据库 / 所有数据库 | 不是日常缓存清理命令;ASYNC 也不缩小删除范围 |
在 Redis 6.2 的常见模型中,命令执行主要由主线程完成,网络 I/O 和部分后台工作可由其他线程或进程承担。不能据此说“Redis 不会受 CPU 限制”:大集合操作、长 Lua 和高频请求都可能让命令执行成为瓶颈。
12. 必须配套理解的运行机制
12.1 过期与内存淘汰是两回事
过期回答“这个值还能有效多久” ;淘汰回答“内存到上限后让谁腾位置”。带 TTL 的键可能在到期前就被淘汰,没有 TTL 的键也可能被 allkeys-* 策略淘汰。
| 策略 | 含义 | 常见选择依据 |
|---|---|---|
noeviction | 不主动淘汰;受内存限制的写命令会失败 | 不能随意丢数据时,需要容量保障和写失败处理 |
allkeys-lru | 在所有键中近似淘汰最近较少使用的键 | 常见缓存场景 |
allkeys-lfu | 在所有键中近似淘汰低频访问键 | 访问频率能较好体现热点时 |
volatile-lru / volatile-lfu | 只从带 TTL 的键中按上述规则选择 | 没有可淘汰键时仍可能写入失败 |
volatile-ttl | 优先淘汰即将到期的键 | TTL 能反映保留价值时 |
LRU 关注最近用没用,LFU 关注一段时间内用得多不多;Redis 的实现是近似算法。可重建的缓存,与锁、消息队列等不希望被淘汰的数据,通常应分开配置实例。策略选择见 Key eviction。
12.2 持久化、复制、哨兵、集群分别解决什么
| 机制 | 解决的问题 | 工作中要记住的边界 |
|---|---|---|
| RDB 快照 | 保存某个时刻的数据状态,用于恢复和备份 | 故 障可能丢失最近快照之后的更新 |
| AOF 日志 | 记录写入以便重放恢复 | appendfsync everysec 通常存在约一秒的数据丢失窗口,并非零丢失保证 |
| 主从复制 | 把主节点数据复制给副本 | 默认异步,从节点可能读到旧值,切换可能丢失已确认写入 |
| Sentinel 哨兵 | 监测主从并自动故障切换,供客户端发现主节点 | 不负责把数据拆到多个分片 |
| Cluster 集群 | 把数据按槽分配给多个主节点,并配合副本故障切换 | 有多键同槽约束,不能任意跨节点执行事务或脚本 |
BGSAVE 在后台子进程生成快照;后台执行仍有创建子进程时的暂停,以及额外内存和磁盘开销。AOF 重写是压缩恢复当前数据所需的表示,不是把业务数据清空。持久化和副本都不代替独立备份与恢复验证。见 持久化。
WAIT 可以等待副本确认收到写入,但不把异步主从变成强一致系统,也不等价于落盘保证。复制和自动切换的边界见 复制 与 Sentinel。
12.3 Cluster 的同槽限制
Redis Cluster 把键分配到 16384 个槽,再把槽分配给各主节点。多键命令、事务和 Lua 脚本通常要求涉及的键处在同一个槽;MGET、多键 DEL、集合交集也在检查范围内。
需要共同操作的少量关联键,可以使用相同的 hash tag,也就是键名中花括号里的内容:
SET demo:user:{42}:name "小王"
SET demo:user:{42}:city "上海"
MGET demo:user:{42}:name demo:user:{42}:city
CLUSTER KEYSLOT demo:user:{42}:name
CLUSTER KEYSLOT demo:user:{42}:city
这些键共享 {42},槽位相同。连接集群时可给 redis-cli 加 -c 跟随重定向;普通单机执行 CLUSTER KEYSLOT 会报未启用集群。不要给所有键使用同一个 tag,否则会把大量数据挤进一个槽,失去分片意义。
有的客户端会把跨槽批量读取拆成多次请求,但这不再是服务端单条命令的原子读取;必须核对客户端行为。详见 Redis Cluster。
13. 按需查阅:消息、位图、估算和地理位置
13.1 Pub/Sub 与 Stream
| 需求 | 优先考虑 | 基本命令 |
|---|---|---|
| 在线订阅者即时接收通知,可容忍丢失 | Pub/Sub | PUBLISH、SUBSCRIBE、UNSUBSCRIBE |
| 保存消息、消费者分工、确认与重试 | Stream | XADD、XREADGROUP、XACK、XPENDING、XAUTOCLAIM |
Pub/Sub 不保存消息供离线订阅者补读;发布命令返回了订阅者数量,也不意味着业务已处理成功。见 Pub/Sub。
Stream 最小流程如下,在测试环境按顺序执行。创建消费者组只需执行一次,重复创建会报 BUSYGROUP。
XGROUP CREATE demo:events workers 0 MKSTREAM
XADD demo:events * type order-created orderId 1001
XREADGROUP GROUP workers worker-1 COUNT 10 BLOCK 5000 STREAMS demo:events >
XPENDING demo:events workers
0 表示消费者组从头处理,MKSTREAM 在键不存在时创建 Stream;* 让 Redis 生成消息 ID。> 读取尚未投递给该组任何消费者的新消息,BLOCK 5000 的单位是毫秒。业务成功后,把下面的 消息ID 替换为实际返回值再确认:
XACK demo:events workers 消息ID
确认只移除该组的待确认记录,不会删除 Stream 中的消息。消费者崩溃后,未确认消息仍要主动回收处理,例如:
XAUTOCLAIM demo:events workers worker-2 60000 0-0 COUNT 10
这会尝试接管空闲至少 60000 毫秒的待确认消息;按返回游标继续,直到游标为 0-0,后续再周期检查。接 管后重新处理,成功后 XACK。正常任务也可能运行很久,因此接管阈值要匹配业务耗时;消费者必须能处理重复投递。
消息不被提前删除、持久化与复制配置符合要求,且应用维护确认和重试流程,才能形成可靠的消费方案。还应限制重试次数、安排失败消息去向,并监控积压。保留长度可用 XADD ... MAXLEN ~ 数量 或 XTRIM 控制,但裁剪可能删除尚未处理的消息,不能照抄一个固定长度就认为安全。
参考:XGROUP CREATE、XREADGROUP、XAUTOCLAIM、XTRIM。
13.2 其他三个实用方向
| 场景 | 命令示例 | 必须知道的限制 |
|---|---|---|
| 按日期记录用户是否签到 | SETBIT demo:signin:2026-09-17 42 1;GETBIT demo:signin:2026-09-17 42;BITCOUNT demo:signin:2026-09-17 | 位图用一个二进制位表示有无;稀疏、极大的用户 ID 会撑大内存,应先映射或分段 |
| 统计当天近似去重访客数 | PFADD demo:uv:2026-09-17 user:42 user:43;PFCOUNT demo:uv:2026-09-17 | HyperLogLog 只估算数量,不能列出成员或精确判断某人是否来过 |
| 查附近门店 | GEOADD demo:shops 121.4737 31.2304 shop:1;GEOSEARCH demo:shops FROMLONLAT 121.47 31.23 BYRADIUS 5 km ASC COUNT 10 WITHDIST | 参数先经度后纬度,查询返回半径内的候选门店;不是道路导航距离 |
位图偏移量会影响内存分配,见 SETBIT。HyperLogLog 的标准误差约为 0.81%,这不是每次结果误差的硬上限,见 HyperLogLog。附近搜索语法见 GEOSEARCH。
14. 工作时的使用顺序
拿到需求,先确定用哪种数据结构,再确定键的范围、生命周期、并发要求和数据丢失容忍度。
- 普通读写先用原生命令:缓存
SET/GET,计数INCR,去重SADD,排行ZADD/ZRANGE。 - 有多步依赖就检查执行边界:只是减少网络往返用 Pipeline;读取后要判断再写入时,选择
WATCH或短 Lua。 - 有增长就限制大小:缓存有 TTL,列表和消息有保留策略,批量请求有大小上限。
- 上线前覆盖三类例子:键不存在、键已存在且有 TTL、两个请求同时操作;还要明确超时后能否重试。
- 出问题先定位范围:实例和数据库 → 键类型与 TTL → 内存与慢日志 → 连接、复制和应用行为。
第一轮只记第 1 节速查表;第二轮练习各类型的小例子;实际接入业务前再对照第 9~12 节检查组合操作与运行边界。这样记住的命令才能转化为可用的工作方法。
15. 常用命令完整操作案例
本节使用独立的 demo:case: 测试键。只在测试实例执行开头的 DEL,它用于重置本案例列出的键,返回数量取决于是否已做过该案例。后续按顺序执行,并在示例 TTL 到期前核对结果。
命令块只包含输入,可复制到 redis-cli;结果表用逻辑值表达,省略客户端显示的序号、转义和 (integer) 等包装。预期结果根据命令语义推演,本节没有在 Redis 实例上实际执行;TTL、扫描游标、内存和日志内容以实际返回为准。
| 案例 | 业务需求 | 主要命令 |
|---|---|---|
| 15.1 | 商品详情缓存、批量读取、更新后失效 | SET、GET、MGET、DEL、TTL |
| 15.2 | 会话续期、保留 TTL、领取一次性结果 | GETEX、SET ... KEEPTTL、GETDEL |
| 15.3 | 访问计数与过期检查 | INCR、INCRBY、EXPIRE、TTL |
| 15.4 | 购物车增减商品、修改字段、删除商品 | HSET、HINCRBY、HMGET、HDEL |
| 15.5 | 点赞去重、取消点赞、找共同关注 | SADD、SISMEMBER、SCARD、SINTER |
| 15.6 | 保留最近记录、按顺序领取任务 | LPUSH、LTRIM、RPUSH、BLPOP |
| 15.7 | 更新积分、查看名次与分数区间 | ZADD、ZINCRBY、ZRANGE、ZREVRANK |
| 15.8 | 按前缀定位并删除指定测试缓存 | SCAN、UNLINK、EXISTS |
| 15.9 | 两个客户端修改同一值时检测冲突 | WATCH、MULTI、EXEC |
| 15.10 | 60 秒内最多放行 3 次请求 | EVAL、INCR、EXPIRE |
| 15.11 | 竞争互斥锁,验证不能误删他人的锁 | SET ... NX PX、EVAL |
| 15.12 | 排查类型不符和遗漏过期时间 | TYPE、TTL、HGET、MEMORY USAGE |
15.1 商品详情缓存:从未命中到更新失效
需求:商品 1001 的详情缓存 5 分钟,数据库更新价格后让旧缓存失效。
先模拟首次访问。SET 中的数据代表应用查数据库后得到的结果,Redis 本身不会自动查数据库。
DEL demo:case:product:1001 demo:case:product:1002
GET demo:case:product:1001
SET demo:case:product:1001 '{"name":"keyboard","price_cents":19900}' EX 300
GET demo:case:product:1001
TTL demo:case:product:1001
MGET demo:case:product:1001 demo:case:product:1002
| 操作 | 预期结果 | 应用怎么处理 |
|---|---|---|
第一次 GET | 空值 | 缓存未命中,去数据库查询 |
SET ... EX 300 | OK | 详情和 300 秒有效期一起写入 |
第二次 GET | {"name":"keyboard","price_cents":19900} | 反序列化后返回详情 |
TTL | 刚写入时接近 300,随后递减 | 验证确实设置了过期时间 |
MGET | [商品 1001 的 JSON, 空值] | 结果按输入键顺序对应,1002 单独回源 |
假设应用已经在数据库中将价格更新为 18900 分,并成功提交事务,再执行:
DEL demo:case:product:1001
GET demo:case:product:1001
SET demo:case:product:1001 '{"name":"keyboard","price_cents":18900}' EX 300
GET demo:case:product:1001
在原键未过期的前提下,删除返回 1,紧接的读取为空,重新填充后读到新价格。最后的 SET 模拟下一次请求查库后的回填。这里演示正常路径,并发旧值回填和删除失败按第 10.1 节处理。
15.2 会话续期:读一次,再保留或修改有效期
需求:会话初始有效期 5 分钟,访问后延长为 30 分钟;修改会话内容时保留当前剩余时间。
DEL demo:case:session:abc
SET demo:case:session:abc "user:42" EX 300
GETEX demo:case:session:abc EX 1800
TTL demo:case:session:abc
SET demo:case:session:abc "user:42:verified" KEEPTTL
GET demo:case:session:abc
TTL demo:case:session:abc
GETEX 返回原值 user:42,同时把有效期重新设为 1800 秒。修改后 GET 返回 user:42:verified;两次 TTL 均为剩余时间,后一次通常更小,不应变为 -1。普通 GET 不续期。
另一个场景:临时结果只能被领取一次。
DEL demo:case:result:job:42
SET demo:case:result:job:42 "download-ready" EX 300
GETDEL demo:case:result:job:42
GETDEL demo:case:result:job:42
第一次领取返回 download-ready,第二次为空。如果第一次已经删除但网络响应丢失,重试也读不到原结果;需要可靠重试时,应保留结果并维护领取状态。相关语义见 GETEX 和 GETDEL。
15.3 访问计数:原子累加不等于自动过期
需求:统计一篇文章的访问次数,观察计数命令与 TTL 的关系。
DEL demo:case:views:article:1001
INCR demo:case:views:article:1001
INCR demo:case:views:article:1001
INCRBY demo:case:views:article:1001 10
GET demo:case:views:article:1001
TTL demo:case:views:article:1001
EXPIRE demo:case:views:article:1001 600
INCR demo:case:views:article:1001
TTL demo:case:views:article:1001
| 操作 | 预期结果 |
|---|---|
两次 INCR | 依次返回 1、2 |
INCRBY ... 10 | 返回 12 |
GET | 字符串 "12" |
第一次 TTL | -1,计数键没有自动获得过期时间 |
EXPIRE ... 600 | 返回 1 |
再次 INCR | 返回 13 |
最后 TTL | 不超过 600 的剩余秒数,计数没有清掉原 TTL |
这统计的是次数,同一个用户刷新两次也计两次。这里分开发命令是为了观察行为;业务要求计数与首次过期同时完成时,使用第 15.10 节的脚本。参考:INCR。
15.4 购物车:字段是商品,字段值是数量
需求