数据库

Redis 变慢排查手册:大 key、阻塞命令与内存淘汰

Redis 是单线程处理命令的,一个阻塞点就能拖慢整个实例。本文按「内存水位 → 大 key → 阻塞命令 → 淘汰与持久化」的顺序给出排障路径,每步附可直接执行的命令。

作者:巧匠团队·8 分钟阅读·更新于 2026-08-30

先看内存:满没满、碎不碎

`INFO memory` 是第一站:`used_memory_human` 是实际占用,`maxmemory` 是上限(0 表示未限制——危险,会被操作系统 OOM 杀掉);`mem_fragmentation_ratio` 大于 1.5 说明碎片偏多,频繁删改大 value 的场景常见。

内存满 + 淘汰策略为 noeviction 时,写命令直接报 OOM 错误;应用层的表现往往是「偶发写失败」,容易被误判为网络问题。

redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|mem_fragmentation"
redis-cli CONFIG GET maxmemory

扫描大 key:慢的常见元凶

一个包含百万成员的 ZSET,一条 ZRANGE 全量就可能耗时数秒,把后面所有命令全部排队。用 `redis-cli --bigkeys` 低开销采样各类型最大的 key;要精确体积用 `--memkeys`(需 redis 6.0+ 的扩展)或 MEMORY USAGE 逐个测量。

注意 --bigkeys 是采样,生产上可以放心跑(内部用 SCAN 游标,不阻塞);但绝对不要用 KEYS *,它一次性遍历全库,是线上事故制造机。

redis-cli --bigkeys          # 采样找各类型最大 key(安全)
redis-cli MEMORY USAGE <key>  # 精确测量单个 key

看慢日志:哪条命令在拖后腿

Redis 内置慢日志会记录超过阈值的命令(默认 10ms,建议调到 1ms 以捕获更多线索)。`SLOWLOG GET 10` 查看最近十条,关注命令名、耗时与 key 名,再反查业务代码调用点。

高耗时命令排行榜:KEYS、SMEMBERS 大集合、HGETALL 大哈希、ZRANGE 全量、FLUSHALL/FLUSHDB、Lua 脚本里的长循环。逐个替换:KEYS → SCAN,SMEMBERS → SSCAN 分批,HGETALL → HMGET 取需要的字段。

redis-cli CONFIG SET slowlog-log-slower-than 1000   # 阈值降到 1ms
redis-cli SLOWLOG GET 10

淘汰策略:内存满之后的行为开关

缓存场景选 allkeys-lru(全库 LRU 淘汰)或 allkeys-lfu(热点更准,4.0+);存数据必须持久保留的场景选 noeviction,但必须提前扩容并加告警,否则写失败只是时间问题。volatile-* 系列只淘汰设置了过期时间的 key——如果业务没设 TTL,等于变相 noeviction。

改完策略立即验证:压一轮写入确认不再报 OOM。淘汰触发瞬间可能有轻微延迟抬升(大量 DEL),4.0+ 开启 lazyfree 能把删除异步化。

redis-cli CONFIG SET maxmemory-policy allkeys-lru
redis-cli CONFIG SET lazyfree-lazy-eviction yes

持久化抖动:RDB fork 与 AOF 刷盘

RDB 的 BGSAVE 需要 fork 子进程,实例越大 fork 越慢(页表复制),期间主线程会短暂停顿——大实例周期性卡顿时先查 INFO stats 的 latest_fork_usec。AOF 默认 everysec 是折中值;改 always 会把每次写入都 fsync,吞吐骤降。

如果 Redis 只是纯缓存、数据可重建,直接关持久化(save ""、appendonly no)能消掉一整类抖动来源,也是最常见的「免费」优化。

redis-cli INFO stats | grep latest_fork_usec   # fork 耗时(微秒)

应急与预防清单

线上卡顿应急:① SLOWLOG 定位命令;② 大 key 用 UNLINK(异步删除)代替 DEL;③ 必要时对非关键实例重启。预防:① 把大集合拆小(分片 key);② 全量遍历一律改 SCAN 游标;③ maxmemory 设为物理内存的 70% 以下;④ 监控 used_memory、慢日志条数与主从延迟三个指标。把这套清单贴进值班手册,Redis 故障的 MTTR 能降一半。

redis-cli UNLINK <big-key>   # 异步删除,不阻塞主线程