<p>KEYS * 会卡死 Redis,因其单线程特性需遍历全量键空间,导致不可控长阻塞;应改用 SCAN 分批扫描,避免阻塞主线程。</p>

为什么 KEYS * 会卡死 Redis
Redis 是单线程执行命令的,任何耗时操作都会阻塞后续所有请求。KEYS * 需要遍历整个键空间,数据量稍大(比如几万 key)就可能卡住几百毫秒甚至秒级,这时客户端大量超时、连接堆积、监控报警全来。
它不是“慢”,而是“不可控的长阻塞”——没有进度反馈、无法中断、不区分数据冷热。
- 线上绝对禁用
KEYS *,包括脚本、定时任务、临时排查命令 -
SCAN是唯一安全替代:游标分批、时间可控、不阻塞主线程 - 注意
SCAN不保证一次扫完,需循环调用直到游标返回0
SMEMBERS 和 HGETALL 在大数据量下的真实表现
这两个命令看似只是“取值”,但实际会把整个集合或哈希一次性加载进内存并序列化返回。一个含 5 万成员的 SET,返回体可能达几 MB,不仅拖慢当前连接,还会挤占网络缓冲区和客户端解析资源。
更隐蔽的问题是:它们常被用在“查全部再过滤”的逻辑里,比如“取所有用户 ID 再用代码筛活跃用户”——这本质是把数据库该干的事丢给应用层。
- 改用
SSCAN/HSCAN分批拉取,配合服务端或客户端逻辑过滤 - 如果必须全量,先评估数据规模:
SCARD或HLEN小于 1000 才考虑直接取 - 警惕 ORM 或 SDK 封装的“get_all”类方法,背后可能就是
SMEMBERS
Lua 脚本里藏着的隐形阻塞点
很多人以为 Lua 脚本“原子执行”=“安全”,但 Redis 对 Lua 的限制只在原子性,不限制执行时长。一段遍历 10 万 key 的 for 循环,在 Lua 里照样卡主线程。
常见陷阱包括:用 redis.call('KEYS', '*')、在脚本里做复杂字符串处理、递归遍历嵌套结构。
- Lua 脚本运行时间应控制在毫秒级,超过 10ms 就该拆解或换方案
- 禁止在脚本中调用
KEYS、SMEMBERS、HGETALL等全量命令 - 用
redis.call('SCAN', ...)替代KEYS,但注意 SCAN 在 Lua 中不支持 match 参数(Redis 7.0+ 才支持)
配置和监控上最容易被忽略的防线
光靠写代码规避不够,Redis 自身配置和外部监控才是兜底。很多团队出了问题才想起看 slowlog,其实它早就在那儿了。
默认 slowlog-log-slower-than 是 10000 微秒(10ms),对高负载实例来说太宽松;而 slowlog-max-len 默认 128 条,高频慢命令很快就被覆盖。
- 生产环境建议设为
slowlog-log-slower-than 1000(1ms),并调大slowlog-max-len到 1024 - 定期用
SLOWLOG GET 10拉最近慢命令,重点看是否出现KEYS、SMEMBERS、长 Lua - 监控项不能只盯
used_memory和connected_clients,要加latest_fork_usec(fork 耗时突增往往预示 RDB/AOF 压力过大)
真正难防的不是某条命令,而是“不知道它正在拖垮服务”。上线前没压测、没开 slowlog、没配监控告警,问题就永远在黑盒里等你重启。










