P99 突然从毫秒跳到秒级,监控上 CPU
不高、网络也正常——常见根因不是「Redis 挂了」,而是
主线程被单条命令或 fork 拖住。第 2
篇讲过主线程执行命令;第 9–11 篇讲过 fork 与
AOF rewrite;第 12 篇讲过复制全量同步。本篇把这些
合成可执行的排障路径:先读信号,再对照机制,最后用
可复现命令 验证假设(本环境未安装
redis-server
时,只给出命令与预期观察维度,不伪造
INFO 数字)。
本文是「Redis / 缓存内核」系列第 15 篇(共 16 篇)。→ 系列目录
版本锚定:Redis OSS 7.4 / 8.x;官方 Latency monitoring、SLOWLOG
、MEMORY` 子命令文档。
一、排障总览:先分「阻塞主线程」还是「资源外因」
flowchart TD
S["Symptom: latency spike / timeout"] --> Q1{"Redis CPU high on main thread?"}
Q1 -->|yes| B["Blocking cmd / big key / O(N) scan"]
Q1 -->|no| Q2{"fork or disk IO?"}
Q2 -->|yes| F["BGSAVE / AOF rewrite / replication full sync"]
Q2 -->|no| Q3{"Memory pressure?"}
Q3 -->|yes| M["eviction / fragmentation / OOM"]
Q3 -->|no| N["network / client pool / Cluster MOVED storm"]
| 信号来源 | 看什么 | 机制锚点 |
|---|---|---|
SLOWLOG GET |
慢命令名、参数规模、耗时 | 主线程单线程执行 |
LATENCY DOCTOR /
LATENCY GRAPH |
事件类型(fork、aof_write、expire) | 官方 latency 框架 |
INFO commandstats |
usec_per_call 异常的命令 |
热路径命令 |
INFO persistence |
rdb_bgsave_in_progress、aof_rewrite_in_progress |
fork 子进程 |
INFO replication |
master_link_status、sync_full |
全量同步 |
MEMORY DOCTOR /
MEMORY STATS |
碎片率、db.overage |
分配器与编码 |
redis-cli --bigkeys /
MEMORY USAGE key |
大 key 尺寸 | 网络与延迟放大 |
二、大 key:延迟、迁移与复制的共同放大器
大 key
指单键值或聚合类型元素过多导致:单命令 \(O(N)\) 过长、序列化/RDB/AOF
膨胀、Cluster MIGRATE 与复制回放卡顿。
诊断命令(在 测试实例 上执行;生产先用
SCAN 采样):
# 扫描可能的大 key(对线上有负载,建议在从库或低峰跑)
redis-cli --bigkeys
# 单 key 精确内存(含编码开销)
redis-cli MEMORY USAGE mykey SAMPLES 5
# 集合类元素个数
redis-cli SCARD myset
redis-cli HLEN myhash
redis-cli XLEN mystream治理方向(与机制一致,非穷尽):
- 拆分:Hash 按 field 分桶;ZSet 按 score
分段;避免百万级
HGETALL。 - 异步删除:
UNLINK替代DEL(lazyfree);大 key 删除仍可能触发后台线程与主线程协调成本。 - 禁止 生产
KEYS *;用SCAN迭代。 - Cluster 迁移期避免对大 key 频繁
MIGRATE失败重试——先缩 key。
三、fork 延迟:持久化与复制的隐形尖刺
BGSAVE、BGREWRITEAOF、复制全量同步均可能
fork() 子进程。Linux 上写时复制使
父进程在 fork 期间
可能遭遇显著延迟(页表复制、内存越大越明显),表现为客户端超时,而
CPU 未必持续高。
观察命令:
redis-cli INFO persistence | egrep 'rdb_|aof_'
redis-cli INFO stats | grep instantaneous_ops_per_sec
redis-cli LATENCY DOCTOR
redis-cli LATENCY GRAPH fork缓解思路(配置层,需结合 SLO):
- 控制
used_memory与实例数,避免 单实例数十 GB 仍频繁 rewrite。 appendfsync everysec下 AOF rewrite 与 fsync 叠加——见 第 10–11 篇 与 fsyncgate。no-appendfsync-on-rewrite yes等权衡 丢失窗口 vs 延迟(官方文档有说明)。- 复制侧增大
repl-backlog-size减少FULLRESYNC(第 12 篇)。
本机复现 fork
敏感度(仅实验环境):启动独立
redis-server,灌入大数据集后手动
BGSAVE,同时对另一终端跑
redis-cli --latency 或
LATENCY GRAPH fork,观察延迟尖刺是否与
fork
事件对齐——具体毫秒数依赖机器内存与内核版本,此处不写入虚构输出。
四、阻塞命令与 O(N) 陷阱
下列命令在 主线程 执行且可能长时间占用(官方命令文档均标注复杂度):
| 命令 / 模式 | 风险 |
|---|---|
KEYS |
全局扫描,生产禁用 |
FLUSHALL / FLUSHDB |
同步删除大量 key |
SUNION / SORT(大集合) |
\(O(N)\) 或更大 |
SAVE(非 BG) |
阻塞至 RDB 完成 |
BLPOP 等阻塞列表 |
阻塞 客户端连接,不阻塞其他客户端命令执行(易与主线程阻塞混淆) |
DEBUG 系列 |
仅调试 |
# 查看最近慢命令(条目数依 slowlog-log-slower-than 配置)
redis-cli SLOWLOG GET 20
redis-cli CONFIG GET slowlog-log-slower-than
redis-cli CONFIG GET slowlog-max-len
# 命令级累计耗时
redis-cli INFO commandstatsLATENCY MONITOR 需显式开启(有开销):
redis-cli CONFIG SET latency-monitor-threshold 100
redis-cli LATENCY LATEST
redis-cli LATENCY HISTORY fork五、内存碎片与 maxmemory 误判
INFO memory 中 used_memory_rss
显著大于 used_memory 时,可能存在
碎片 或
分配器持有页。Redis 4.0+ 提供
ACTIVE DEFRAG(需配置开启)在线整理;代价是额外
CPU。
redis-cli INFO memory
redis-cli MEMORY STATS
redis-cli MEMORY DOCTOR
redis-cli CONFIG GET activedefrag勿把 eviction(第
8 篇)与 fragmentation
混谈:前者是策略淘汰 key,后者是
空闲页无法重用。maxmemory
达到上限但 RSS 仍高时,两条线都要查。
六、Cluster 与复制相关延迟
| 现象 | 检查 |
|---|---|
间歇 -MOVED / -ASK 风暴 |
客户端是否 Cluster-aware;是否正在 resharding |
| 读 replica 陈旧 | INFO replication 的
slave_repl_offset 与 master 差值 |
| 全量同步 | sync_full
增长、master_link_status:down |
redis-cli CLUSTER INFO
redis-cli CLUSTER NODES
redis-cli INFO replication七、可复现清单(测试环境最小步骤)
以下步骤假设本机已安装 Redis 7.4+;若无,跳过执行,仅作排障模板。
A. 慢命令留痕
redis-server --port 6379 --daemonize yes
redis-cli CONFIG SET slowlog-log-slower-than 1000
redis-cli DEBUG POPULATE 100000 foo 100
redis-cli KEYS 'foo*'
redis-cli SLOWLOG GET 5
redis-cli DEBUG POPULATE 0B. 大 key 扫描
redis-cli DEBUG POPULATE 1 bigkey 100000
redis-cli --bigkeys
redis-cli MEMORY USAGE bigkey:0C. Latency 与 fork(需一定内存才有感)
redis-cli CONFIG SET latency-monitor-threshold 1
redis-cli BGSAVE
redis-cli LATENCY LATEST
redis-cli LATENCY GRAPH forkD. 碎片观察(长跑后更有意义)
redis-cli INFO memory
redis-cli MEMORY DOCTOR验证通过标准:命令能跑通;你能把观察到的现象(慢命令名、latency
事件类型、是否
bgsave_in_progress)对应到上文机制——而非对齐某一组虚构毫秒值。
八、常见误解
| 误解 | 事实 |
|---|---|
| 「CPU 低就不是 Redis 问题」 | fork、磁盘
fsync、客户端连接打满都可导致超时且 CPU 不高 |
| 「上 SSD 就没有 AOF 问题」 | fsync 语义与 rewrite 仍可能阻塞或放大延迟(fsyncgate) |
「UNLINK 永远异步」 |
极小 key 仍可能同步路径;大 key 依赖
lazyfree-lazy-user-del 等配置 |
| 「SLOWLOG 为空就没事」 | 阈值以上才记录;P99 恶化可能来自 未超阈值的大量 5ms 命令 |
| 「MEMORY DOCTOR 一行就能修」 | 它给建议方向;是否 activedefrag 需评估 CPU
与版本 |
九、学术谱系、争论与开放问题
谱系:Google Why is my database slow 类经验 → Redis 内置 Latency Monitoring(antirez 博客,B 级)→ 云托管可观测(外部产品,本篇不展开)。
争论:在线 activedefrag vs
重启换页;no-appendfsync-on-rewrite
的丢失窗口是否可接受——取决于 RPO,无社区统一答案。
开放问题:
- 超大实例(\(>64\)GB)上 fork
延迟的可预测模型与 Linux
MADV_DONTNEED等调优,论文多、生产可移植结论少。 - IO threads 能否系统性降低 主线程网络解码 与慢客户端头阻塞——需按 8.x 实测,本篇不宣称比例。
十、小结
三句话小结:
- Redis 排障先分 主线程阻塞(大
key、O(N)、阻塞命令)与 fork/IO/复制
外因,用
SLOWLOG、LATENCY、INFO对齐机制而非猜。 - 大 key 同时伤害延迟、持久化、Cluster
迁移与复制;治理靠拆分、
UNLINK、禁止KEYS,不是单纯加内存。 - 本篇命令清单可在测试实例复现;生产数字必须来自你自己的运行结果,不要套用文章里的虚构
INFO片段。
下一篇收束全系列:选型与阅读地图。
参考资料
文档(A 级)
- Redis Documentation, Latency
monitoring(
LATENCY DOCTOR、LATENCY GRAPH)。 - Redis Documentation, Debugging、Memory
optimization(
MEMORY DOCTOR、--bigkeys)。 - Redis Commands,
SLOWLOG、INFO、UNLINK、SCAN。 - Redis OSS 7.4
src/latency.c、src/debug.c(DEBUG POPULATE仅测试)、src/lazyfree.c。
辅助(B 级)
- RDB、AOF、复制。
- fsync 失败真相。
返回 系列目录 | 上一篇:Memcached 对照 | 下一篇:选型与阅读地图
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Redis / 缓存内核】TTL 与过期:expires dict · 惰性/主动删除 · lazyfree
拆解 Redis 7.4 的 TTL 机制:expires 辅助表、lookup 路径惰性删除、activeExpireCycle 自适应采样,以及 lazyfree 异步释放大对象对延迟的影响。
【Redis / 缓存内核】RDB:fork COW · BGSAVE · 快照格式
从 rdb.c 拆解 SAVE/BGSAVE 的 fork 写时复制语义、子进程快照路径,以及 REDIS 魔数与 opcode 锚定的 RDB 格式边界。
【Redis / 缓存内核】maxmemory 策略:近似 LRU/LFU 与 MEMORY DOCTOR
从 evict.c 拆解 volatile/allkeys 与 LRU/LFU/random/ttl 策略矩阵、24 位时钟近似 LRU、淘汰池采样,并对照算法篇真 LRU 与 MEMORY DOCTOR 信号。
【Redis / 缓存内核】AOF:命令日志 · rewrite · appendfsync
从 aof.c 拆解 AOF 多段清单、feedAppendOnlyFile 写路径、后台 rewrite 与 appendfsync always/everysec/no 各自的丢失窗口及 fsync 失败语义。