Redis KEYS 和 SCAN 有什么区别?线上为什么禁用 KEYS?
简化版
KEYS 会一次性遍历整个 keyspace,数据量大时会阻塞 Redis 主线程;SCAN 是游标式、分批返回、渐进遍历,更适合线上排查和后台任务。面试要强调:SCAN 也不是强一致快照,可能重复、可能漏掉遍历期间变化的 key,使用方必须能容忍并做去重。
详细版
KEYS pattern 的问题在于它要扫描所有 key,并一次性返回匹配结果。Redis 大部分命令在主线程执行,如果线上有几百万个 key,KEYS * 可能让正常读写请求排队,造成延迟尖刺。
SCAN cursor MATCH pattern COUNT n 通过游标渐进遍历,每次只返回一批数据。它不会长时间独占主线程,适合后台清理、统计、迁移前抽样和线上排查。
但 SCAN 不是事务快照。遍历过程中如果 key 新增、删除或 rehash,结果可能重复,也可能看不到某些变化。工程上要做到:控制 COUNT、分页处理、幂等执行、必要时去重,不要把 SCAN 当成强一致全量列表。
完整版教学
一、为什么 KEYS 在线上危险
Redis 常被说成“单线程”,更准确地说是核心命令执行路径高度依赖主线程。KEYS 的语义是从当前数据库里找出所有匹配 pattern 的 key,所以它必须遍历字典。
如果实例里有 300 万个 key,即使每个 key 检查只花 0.5 微秒,总扫描时间也可能达到 1.5 秒。对 Redis 来说,这 1.5 秒不是后台慢慢做,而是可能挡住后面的普通 GET、SET、INCR。
面试里不要只说“KEYS 慢”,要说清楚慢在哪里:它是 O(N) 遍历,而且返回结果可能很大,既消耗 CPU,也消耗网络和客户端内存。
请求队列:
GET user:1 -> KEYS * -> SET order:1 -> GET cfg
KEYS 执行期间,后面的 SET/GET 只能排队等待
二、SCAN 的游标模型是什么
SCAN 每次调用都传入上一次返回的 cursor。第一次从 0 开始,服务端返回新的 cursor 和一批 key;当 cursor 再次变成 0,说明一次遍历结束。
它不是“服务端创建一个列表然后分页”,而是基于 Redis 字典结构做渐进扫描。因此每次只做一小段工作,降低单次阻塞时间。
常见用法如下:
SCAN 0 MATCH user:* COUNT 1000
SCAN 3567 MATCH user:* COUNT 1000
COUNT 1000 只是提示,不保证一定返回 1000 个。Redis 可能返回更少,也可能在某些编码结构里一次返回较多。
三、SCAN 为什么不是强一致快照
SCAN 遍历期间,Redis 里的 key 可能变化。假设你扫描了前半段后,有 key 被删除;或者字典扩容 rehash,某些桶的遍历位置变化,就可能出现重复或遗漏。
这不是 bug,而是 SCAN 用低阻塞换来的语义边界。它适合“尽量遍历一遍”的任务,不适合“必须得到某一瞬间完整 key 集合”的任务。
| 命令 | 是否阻塞风险高 | 是否一次返回全部 | 是否快照一致 |
|---|---|---|---|
KEYS | 高 | 是 | 接近当前瞬间结果 |
SCAN | 低 | 否 | 否 |
| 业务索引集合 | 取决于设计 | 可分页 | 可由业务保证 |
四、线上如何安全使用 SCAN
第一,控制 COUNT。如果实例很忙,COUNT 可以从 100 或 500 起步;如果是低峰后台任务,可以提高到 1000 或 5000。关键是观察 Redis 延迟,而不是死背一个数字。
第二,处理逻辑必须幂等。例如扫描 cache:tmp:* 并删除,重复扫到同一个 key 时,DEL 再执行一次也应该安全。
第三,批处理要限速。每批扫描后可以在任务侧 sleep 几十毫秒,或者按业务 QPS 令牌控制,避免“命令不阻塞,但后台任务把实例打满”。
五、什么时候不该依赖 SCAN
如果业务经常需要按条件列出 key,说明 keyspace 被当成数据库索引用了。Redis 的 key 字典不是业务查询引擎,不能替代表索引。
例如“找出某个租户下所有活跃用户缓存”,更稳的做法是维护一个集合:SADD tenant:1:active_users userId,再按集合分页或异步维护。
对强一致要求高的批处理,例如“必须给所有未过期券发通知且不能漏”,也不应只靠扫描 key,应该在数据库或消息系统里维护可追踪状态。
六、常见误区与追问
- 误区:
SCAN一定不会影响线上。 它只是降低单次阻塞,调用过密、COUNT过大或匹配结果过多仍然会制造压力。 - 误区:
COUNT就是返回条数。COUNT是工作量提示,不是严格 limit。 - 误区:
SCAN能保证遍历期间不漏不重。 它不是快照遍历,业务侧要能容忍重复和变化。 - 追问:删除大量 key 用什么? 用
SCAN分批找 key,再配合UNLINK异步释放内存,比一次性DEL大量 key 更稳。 - 追问:为什么
KEYS user:*在测试环境没问题? 测试环境 key 少、并发低,线上数据量和请求队列会把 O(N) 成本放大。
七、加强记忆
记忆钩子:
KEYS像让 Redis 停下来翻完整本通讯录;SCAN像边走边翻页,但翻页过程中通讯录还在被别人改,所以结果只能用于可容忍重复和变化的任务。
回答这题时按“阻塞原因 → 游标模型 → 非快照语义 → 工程用法 → 替代设计”组织最稳。真正的亮点不是背出 SCAN 命令,而是能说明它为什么安全一些、为什么仍然不能滥用。