scan 替代 keys 是唯一靠谱解法:因 keys 阻塞主线程致 redis 假死,scan 以游标分批迭代、不阻塞,需配 match 和 count 使用,php 中须手动循环游标,redis-cli --scan 适合运维排查,同时需优化内核与 redis 配置。

用 SCAN 替代 KEYS * 是唯一靠谱解法
Redis 一执行 KEYS * 就卡死、响应变慢、甚至被监控标为“假死”,根本原因就是它会阻塞主线程,逐个比对所有 key —— 数据量到百万级时,几秒到几十秒的阻塞很常见。这不是 Redis 慢,是设计上就禁止这么用。
实操建议:
-
SCAN是游标式迭代,每次只返回一批 key(默认 10 个),不阻塞服务,适合线上环境 - 必须配合
match和count参数使用,否则默认返回极少结果,容易漏数据 - 例如:用
SCAN 0 MATCH user:* COUNT 500从游标 0 开始,每次取最多 500 个匹配 key - 注意游标返回值:当新游标为
0时,表示扫描完成;中间游标非零,需继续调用
PHP 里别再写 $redis->keys($pattern)
很多老项目在 PHP 中直接调用 keys() 方法,尤其在缓存清理、后台扫描等场景,一触发就拖垮整个 Redis 实例,连带 Web 请求超时。
实操建议:
- 改用
scan()方法,并手动维护游标循环,不能只调一次 - 示例关键逻辑:
for ($cursor = 0; ; $cursor = $nextCursor) { $keys = $redis->scan($cursor, $pattern, 200); list($nextCursor, $batch) = $keys; foreach ($batch as $key) { /* 处理 */ } if ($nextCursor == 0) break; } - 务必设
count(如 200),否则默认仅返回约 10 个,循环次数爆炸,网络开销反而更大 - 避免在高并发接口中做全量扫描 —— 即使是
SCAN,频繁调用仍会抬高 CPU 和延迟
redis-cli --scan 是运维排查的快捷入口
开发或运维临时查某类 key 分布、确认过期策略是否生效、或统计前缀用量时,不需要写代码,redis-cli 自带的扫描能力足够快又安全。
实操建议:
- 基础用法:
redis-cli --scan --pattern "session:*",输出所有匹配 key,无游标、无分页,但不阻塞服务 - 配合管道过滤:
redis-cli --scan --pattern "log:*" | head -20快速采样 - 想统计数量?别用
wc -l管道计数(可能截断),改用redis-cli --scan --pattern "tmp:*" | wc -l是安全的,因为--scan本身无缓冲限制 - 注意:该命令走的是
SCAN底层,但不支持count调优,大数据量下略慢于手动控制游标的方案
别忽略内核和配置层面的协同优化
就算代码全换成了 SCAN,如果 Redis 运行在内存吃紧、内核过度回收的机器上,照样会抖动甚至假死 —— 尤其是大量 key 过期 + 主动扫描叠加时。
实操建议:
- 检查并设置
vm.overcommit_memory=1(写入/etc/sysctl.conf并sysctl -p),避免 Linux 内核因内存分配策略误杀 Redis - 在
redis.conf中明确配置maxmemory和淘汰策略,例如:maxmemory 2gb+maxmemory-policy allkeys-lru - 禁用
save持久化(设为save "")或改用AOF + everysec,减少 fork 阻塞风险 -
SCAN不是银弹:它不保证一次性扫完全部 key(期间新增/删除会影响结果),也不提供排序或总数,需要业务层接受“最终一致性”语义
Redis 的“假死”往往不是单点问题,而是 KEYS 触发阻塞 + 内存压力 + 内核策略三者咬合的结果。真正稳住的关键,是把扫描行为从“强一致、全量、立即返回”切换成“渐进、可控、可中断”的模式 —— 这个转变,比调任何参数都重要。










