数据库基础体系 · 第 123/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

Redis 监控与延迟诊断:Slowlog、Latency、内存、复制和热点

Redis 的性能问题通常不会直接表现为“某条命令很慢”。客户端看到的延迟,可能来自命令排队、Redis 主线程执行、网络传输、事件循环被阻塞、内存分配、复制缓冲区、持久化子进程创建,或者热点数据导致的资源争用。

因此,诊断 Redis 延迟时需要先区分几个概念:

  • Slowlog:记录执行时间超过阈值的命令。
  • Latency Monitor:记录 Redis 内部特定事件的延迟尖峰。
  • 客户端延迟:从客户端发出请求到收到响应的完整时间。
  • 内存诊断:判断是否发生淘汰、分配器膨胀、大 Key 或碎片问题。
  • 复制诊断:判断主从同步、复制缓冲和重同步是否造成压力。
  • 热点诊断:判断是否少数 Key、命令、分片或客户端集中消耗资源。

这些工具观察的是不同时间区间和不同层次。不能用 Slowlog 单独解释所有延迟,也不能看到 used_memory 正常就断定 Redis 没有内存问题。


一、先建立延迟模型:客户端看到的时间由什么组成

设客户端一次请求的端到端延迟为:

Lclient=Lqueue+Lexecute+Loutput+Lnetwork+LclientL_{\text{client}} = L_{\text{queue}} + L_{\text{execute}} + L_{\text{output}} + L_{\text{network}} + L_{\text{client}}

其中:

  • LqueueL_{\text{queue}}:请求进入 Redis 后,在事件循环或其他队列中等待的时间;
  • LexecuteL_{\text{execute}}:Redis 执行命令的时间;
  • LoutputL_{\text{output}}:生成响应、写入输出缓冲区所需的时间;
  • LnetworkL_{\text{network}}:响应通过网络传输的时间;
  • 最后一个 LclientL_{\text{client}}:客户端线程调度、连接池排队、反序列化等客户端开销。

Slowlog 主要观察 LexecuteL_{\text{execute}},而不是完整的 LclientL_{\text{client}}

例如:

  1. 客户端发送一个 GET
  2. Redis 已经被另一个 KEYS * 长时间占用;
  3. GET 在事件循环中等待;
  4. GET 真正执行只需要几十微秒;
  5. 客户端仍然可能观察到数百毫秒延迟。

此时:

  • 客户端监控会看到一次高延迟;
  • Slowlog 可能没有 GET 记录;
  • Latency Monitor 可能记录到事件循环或命令执行尖峰;
  • 真正的根因可能是前一个阻塞命令。

因此,诊断至少要同时看:

客户端延迟
    ↓
Redis 命令执行时间 —— Slowlog、INFO commandstats
    ↓
Redis 内部延迟事件 —— LATENCY
    ↓
内存、复制、持久化、网络和热点 —— INFO、MEMORY、复制指标、客户端采样

二、Slowlog:记录“执行过慢”的命令

2.1 Slowlog 的定义和边界

Redis Slowlog 是一个存放在内存中的有限长度日志,记录执行时间超过配置阈值的命令。

核心配置包括:

CONFIG GET slowlog-log-slower-than
CONFIG GET slowlog-max-len

例如:

1) "slowlog-log-slower-than"
2) "10000"
1) "slowlog-max-len"
2) "128"

这里的含义是:

  • slowlog-log-slower-than:阈值,单位是微秒;
  • slowlog-max-len:最多保留多少条记录;
  • 阈值为 10000 表示执行时间超过约 10 毫秒的命令才进入 Slowlog;
  • Slowlog 超过最大长度后,会淘汰较旧记录;
  • Slowlog 存储在 Redis 内存中,但通常不参与 RDB/AOF 持久化。

查看记录:

SLOWLOG GET 10

典型返回结构如下:

1) 1) (integer) 42
   2) (integer) 1710000000
   3) (integer) 15320
   4) 1) "HGET"
      2) "user:1001"
      3) "profile"
   5) "10.0.0.12:53124"
   6) "app-1"

一条记录通常包含:

  1. Slowlog ID;
  2. Unix 时间戳;
  3. 执行耗时,单位微秒;
  4. 命令及其参数;
  5. 客户端地址;
  6. 客户端名称,如果设置过。

Slowlog ID 单调递增。读取后不能假设 ID 连续,因为日志会淘汰旧记录。

清空记录:

SLOWLOG RESET

这是破坏性操作,会删除当前 Slowlog 内容。生产环境中通常先导出记录,再执行清理。


2.2 Slowlog 记录的是什么时间

Slowlog 的关键语义是:它记录 Redis 处理命令时的执行耗时,不包括完整的网络往返时间。

因此,以下情况可能导致客户端延迟高,但 Slowlog 没有对应记录:

  • 客户端到 Redis 的网络延迟高;
  • Redis 响应在 TCP 缓冲区中等待发送;
  • 客户端连接池排队;
  • Redis 在执行其他命令,当前命令尚未开始执行;
  • 客户端自身线程被阻塞。

反过来,以下命令可能进入 Slowlog:

LRANGE large:list 0 -1
SMEMBERS large:set
HGETALL large:hash
ZRANGE large:zset 0 -1 WITHSCORES
SCAN 0 COUNT 10000

它们的复杂度通常与返回元素数量有关。即使命令本身的算法复杂度可接受,一次性返回几十万项也可能消耗大量 CPU,并且增加响应序列化和网络传输压力。

2.3 配置阈值的选择

可以临时调整阈值:

CONFIG SET slowlog-log-slower-than 5000
CONFIG SET slowlog-max-len 1024

这表示记录执行时间超过约 5 毫秒的命令,并保留最多 1024 条。

需要注意:

  • CONFIG SET 是否可用取决于 Redis 配置和 ACL 权限;
  • 运行时修改不一定会自动写入配置文件;
  • 若需要重启后保留,应根据部署方式执行 CONFIG REWRITE,并确认配置文件可写;
  • 阈值太高会漏掉大量慢命令;
  • 阈值太低会记录大量正常波动,增加分析噪声;
  • Slowlog 本身是内存结构,slowlog-max-len 不宜无限增大。

一个合理的诊断过程不是永久把阈值设为极低,而是:

  1. 先按业务 SLO 设定观察阈值;
  2. 读取多个时间窗口的 Slowlog;
  3. 统计命令类型、参数规模、客户端来源;
  4. 结合客户端延迟和 LATENCY 判断是否同一时间发生;
  5. 诊断结束后恢复合适阈值。

2.4 Slowlog 结果不能直接等同于“最耗 CPU 的命令”

Slowlog 按单次执行耗时记录命令,不按累计 CPU 时间排序。

例如:

  • 命令 A 每次执行 20 毫秒,每秒执行 1 次;
  • 命令 B 每次执行 1 毫秒,每秒执行 10,000 次。

A 更容易出现在 Slowlog 中,但 B 的总 CPU 消耗可能远高于 A。

因此还需要查看命令统计:

INFO commandstats

典型字段:

cmdstat_get:calls=1000000,usec=800000,usec_per_call=0.80
cmdstat_hgetall:calls=1000,usec=500000,usec_per_call=500.00

含义是:

  • calls:调用次数;
  • usec:累计执行微秒数;
  • usec_per_call:平均每次执行微秒数。

可以用一个简单模型估计某类命令的累计执行贡献:

Ttotal=Ncalls×TaverageT_{\text{total}} = N_{\text{calls}} \times T_{\text{average}}

其中:

  • NcallsN_{\text{calls}} 是调用次数;
  • TaverageT_{\text{average}} 是平均执行时间;
  • TtotalT_{\text{total}} 是这类命令累计占用的执行时间。

这个模型不等于精确 CPU 利用率,因为还会受到多线程 I/O、系统调用、后台线程和采样周期影响,但适合判断“少量很慢”与“大量略慢”哪一种更重要。


三、Latency Monitor:观察 Redis 内部延迟尖峰

3.1 Slowlog 与 Latency Monitor 的区别

Latency Monitor 不是 Slowlog 的另一个展示界面。

它的目标是记录 Redis 内部若干延迟事件,例如:

  • 命令执行延迟;
  • 事件循环延迟;
  • fork 延迟;
  • AOF 写入、同步相关延迟;
  • 过期键处理等内部事件。

具体事件类别和可观察内容受 Redis 版本影响,应以当前版本的 LATENCY HELP 和官方命令文档为准。

启用延迟监控:

CONFIG SET latency-monitor-threshold 5

这里的单位是毫秒。设置为 0 表示关闭。

查看帮助:

LATENCY HELP

查看最近的延迟事件:

LATENCY LATEST

查看历史:

LATENCY HISTORY command
LATENCY HISTORY fork

查看图形化的文本输出:

LATENCY GRAPH command

让 Redis 对当前记录的延迟问题给出诊断建议:

LATENCY DOCTOR

清空延迟历史:

LATENCY RESET

LATENCY RESET 会清理记录,不会修复产生延迟的原因。执行前应保存已有结果。


3.2 为什么要同时看 commandfork

考虑一次 RDB 保存:

  1. Redis 主进程调用 fork 创建子进程;
  2. 操作系统复制页表;
  3. 子进程遍历内存并写入 RDB;
  4. 主进程继续接收命令;
  5. 主进程对被修改的内存页执行写时复制。

如果 Redis 使用了很大的地址空间,fork 本身可能造成一次明显的主线程停顿。此时:

  • Slowlog 可能记录被延迟执行的业务命令,也可能不记录;
  • Latency Monitor 可能记录 fork 或事件循环相关延迟;
  • 客户端会看到多个命令同时变慢;
  • CPU、内存带宽和 RSS 可能在保存期间升高。

这与一条 HGETALL 执行过慢的处理方式不同。前者应检查持久化和内存规模,后者应检查数据结构大小和访问方式。


3.3 Latency Monitor 的边界

Latency Monitor 记录的是 Redis 内部已定义的事件,不是完整的分布式追踪系统。它通常不能告诉你:

  • 哪个业务请求的 Trace ID;
  • 哪个上游服务等待了多久;
  • 完整的网络往返分解;
  • 某个 Key 在全局访问中的精确占比。

因此,生产环境通常采用三层观测:

  1. 客户端埋点或代理层:记录端到端延迟;
  2. Redis Slowlog 与 INFO commandstats:记录命令维度;
  3. Latency Monitor、系统指标和复制指标:记录内部事件和资源状态。

Latency Monitor 默认通常关闭,打开后仍需评估采集成本和记录噪声。它不是持续替代客户端延迟监控的工具。


四、从一次高延迟到根因:一个完整诊断算例

假设应用报告 GET user:1001 的 P99 从 2 毫秒升到 300 毫秒。

第一步:确认客户端延迟是否真实发生

先在客户端或代理层确认:

  • 延迟是否只发生在一个连接;
  • 是否集中在某个 Redis 节点;
  • 是 P99 上升还是所有请求变慢;
  • 请求发送时间与响应时间是否都可获得。

如果只有一个客户端连接变慢,可能是客户端线程、连接池或网络问题,不应立即修改 Redis 配置。

第二步:读取 Slowlog

SLOWLOG GET 128

假设发现:

... "KEYS" "*", duration=280000us
... "GET" "user:1001", duration=12us

这说明 GET 本身只执行了约 12 微秒,而 KEYS * 执行了约 280 毫秒。

推导过程是:

  1. Redis 事件循环在同一执行路径上处理命令;
  2. KEYS * 需要遍历整个 Key 空间;
  3. 在它执行期间,其他命令无法正常获得执行机会;
  4. GET 只能等待;
  5. 客户端看到的是等待时间加上 GET 执行时间;
  6. 因而 GET 的客户端延迟很高,但 GET 不一定成为 Slowlog 中的慢命令。

修复方向不是给 GET 加缓存,而是移除线上 KEYS *,改用增量式 SCAN,并限制每次迭代的工作量:

SCAN 0 MATCH user:* COUNT 100

SCAN 返回:

1) "下一次游标"
2) 1) "user:1001"
   2) "user:1002"

必须使用返回的游标继续扫描,直到游标回到 "0"COUNT 是提示值,不是严格保证每次返回多少项,也不保证单次工作量固定。

第三步:查看 Latency Monitor

LATENCY LATEST

如果同时出现 command 事件尖峰,说明 Redis 命令执行确实存在长尾。如果出现 fork 事件,则还要检查是否与 RDB、AOF 重写或其他后台操作同一时间发生。

第四步:检查内存和数据规模

INFO memory
MEMORY USAGE user:1001

如果某些集合或哈希非常大,可能存在类似问题:

HGETALL giant:hash
SMEMBERS giant:set
LRANGE giant:list 0 -1

这类命令不仅执行时间可能变长,返回数据还会增加输出缓冲和网络发送时间。


五、内存监控:已用内存、RSS、碎片和数据结构

5.1 used_memory 与 RSS 不是同一个概念

查看内存:

INFO memory

常见字段包括:

used_memory
used_memory_human
used_memory_rss
used_memory_peak
used_memory_dataset
used_memory_overhead
maxmemory
maxmemory_policy
mem_fragmentation_ratio
allocator_allocated
allocator_active
allocator_resident

核心区别如下:

  • used_memory:Redis 根据分配器和内部对象统计的已使用内存;
  • used_memory_dataset:数据集本身占用的部分,通常不包括全部管理开销;
  • used_memory_overhead:Key、过期字典、客户端缓冲、复制缓冲等管理开销的统计;
  • used_memory_rss:操作系统看到的 Redis 进程常驻集大小;
  • used_memory_peak:历史峰值;
  • maxmemory:Redis 配置的内存限制;
  • mem_fragmentation_ratio:RSS 与 Redis 内部已用内存的比值,通常近似为:

R=used_memory_rssused_memoryR = \frac{\text{used\_memory\_rss}}{\text{used\_memory}}

这个比值只能作为信号,不能机械地把某个数值直接判定为故障。分配器、操作系统、后台 fork、页回收和版本实现都会影响它。

5.2 碎片率高意味着什么

假设:

used_memory: 8GB
used_memory_rss: 12GB
mem_fragmentation_ratio: 1.5

可能原因包括:

  • Key 大小和生命周期变化剧烈,导致分配器碎片;
  • 删除大量数据后,分配器未立即把内存归还操作系统;
  • RDB/AOF 后台操作期间发生写时复制;
  • RSS 中包含了分配器保留区、共享页或其他进程内存映射;
  • Redis 内部统计和系统 RSS 的统计口径不同。

不能仅凭 mem_fragmentation_ratio=1.5 就执行重启。应结合:

INFO memory
INFO persistence
INFO stats

以及系统级 RSS、交换分区、容器内存限制观察。

如果版本和构建支持主动碎片整理,可以检查:

CONFIG GET activedefrag
CONFIG GET active-defrag-threshold-lower
CONFIG GET active-defrag-threshold-upper

主动碎片整理会消耗 CPU,并不意味着所有碎片都能被消除。开启前应在相同数据模型和流量下测试。

5.3 maxmemory 不是“进程绝不会超过这个值”

maxmemory 控制 Redis 在特定内存管理路径下的数据内存上限,但进程实际 RSS 还可能受到以下因素影响:

  • 分配器额外开销;
  • 客户端输入和输出缓冲区;
  • 主从复制缓冲;
  • AOF 重写、RDB 保存产生的写时复制;
  • 加载数据期间的临时开销;
  • Redis 版本和具体部署模式。

查看淘汰统计:

INFO stats

重点关注:

evicted_keys
evicted_clients
expired_keys
keyspace_hits
keyspace_misses

如果 evicted_keys 持续增长,说明已达到内存管理边界并发生淘汰。若业务依赖缓存命中率,应同时观察:

hit rate=keyspace_hitskeyspace_hits+keyspace_misses\text{hit rate} = \frac{\text{keyspace\_hits}} {\text{keyspace\_hits}+\text{keyspace\_misses}}

当分母不为零时,这个指标可以粗略表示 Key 查询命中率。但它不能区分业务接口,也不能解释为什么某类 Key 被淘汰。

5.4 淘汰策略的语义

常见策略包括:

  • noeviction:达到限制后,写入命令通常返回内存不足错误;
  • allkeys-lru:从所有 Key 中选择近似 LRU 的 Key 淘汰;
  • volatile-lru:只从设置了过期时间的 Key 中选择;
  • allkeys-lfuvolatile-lfu:按近似 LFU 频率选择;
  • allkeys-randomvolatile-random:随机选择;
  • volatile-ttl:优先选择剩余 TTL 较短的 Key。

LRU 和 LFU 都是近似算法,不等于精确维护一个全局排序。Redis 为了控制额外开销,会通过采样等方式选择候选 Key。具体采样参数可通过配置查看,例如:

CONFIG GET maxmemory-policy
CONFIG GET maxmemory-samples

使用 volatile-* 策略时,如果没有足够多的带 TTL Key,可能无法按预期淘汰,从而导致写入失败。选择策略必须建立在业务数据是否允许淘汰、哪些 Key 具有 TTL 之上。


六、大 Key 与内存结构:为什么单个 Key 会制造延迟

Redis 的 Key 数量不大,并不代表数据访问成本低。一个 Key 对应的值可能包含数百万个元素。

检查单个 Key:

TYPE orders:2024
MEMORY USAGE orders:2024
OBJECT ENCODING orders:2024
TTL orders:2024

这些命令的作用是:

  • TYPE:确认数据类型;
  • MEMORY USAGE:估算该 Key 占用的内存,结果可能受采样参数影响;
  • OBJECT ENCODING:查看内部编码;
  • TTL:检查是否有过期时间。

扫描大 Key:

redis-cli --bigkeys -h 127.0.0.1 -p 6379

--bigkeys 的用途是通过扫描统计各类型中的大 Key,并不是访问热点检测。扫描本身会产生 Redis 工作量,生产环境应控制时段、速率和节点范围。

常见风险模式:

HGETALL profile:all
SMEMBERS online:users
LRANGE queue 0 -1
ZRANGE leaderboard 0 -1 WITHSCORES

更稳妥的访问方式通常是分页或范围读取:

HSCAN profile:all 0 COUNT 100
SSCAN online:users 0 COUNT 100
LRANGE queue 0 99
ZRANGE leaderboard 0 99 WITHSCORES

但分页不能自动保证业务语义正确。例如,集合在扫描期间发生修改时,SCAN 系列命令允许出现重复元素,也不能作为严格一致的快照遍历。若业务要求快照语义,应使用专门的数据建模或离线处理方案,而不是把 SCAN 当成事务快照。


七、热点 Key:访问集中造成的局部瓶颈

7.1 什么是热点

热点 Key 是在一个时间窗口内被异常高频访问或更新的 Key。其问题不只在于命令耗时,还可能包括:

  • 单个 Key 让某个 Redis 主线程持续处理大量请求;
  • Redis Cluster 中该 Key 所在槽集中在一个节点;
  • 大 Value 的高频读取占用网络带宽;
  • 高频写入导致复制流量和 AOF 流量集中;
  • 单个 Key 成为分布式锁、计数器或队列的竞争点;
  • 客户端连接集中到一个节点。

需要区分三种热点:

  1. 命令热点:例如 GET 调用总量极高;
  2. Key 热点:大量请求集中访问同一个 Key;
  3. 分片热点:多个热点 Key 落在同一个 Cluster 节点或槽范围。

INFO commandstats 能发现命令热点,但不能直接告诉你具体 Key:

INFO commandstats

SLOWLOG 能看到慢命令的参数,但只覆盖超过阈值的命令,不能统计所有访问频率。

7.2 使用 MONITOR 的风险

MONITOR

会持续输出 Redis 处理的命令。它适合短时间、低流量、隔离环境下确认某个 Key 是否被访问,但不适合作为长期监控手段。

原因包括:

  • 每条命令都要额外格式化并发送;
  • 输出量可能非常大;
  • 可能暴露业务参数和敏感数据;
  • 可能增加 Redis 和监控客户端的负担。

使用时应:

  1. 只授权给必要人员;
  2. 尽量限制观察时间;
  3. 不把输出长期写入日志;
  4. 注意命令参数中的 Token、手机号、用户标识等敏感信息;
  5. 在 Cluster 中分别观察实际承载请求的节点。

更适合生产环境的方式是:

  • 在客户端中对请求做采样,记录命令类型和脱敏后的 Key;
  • 在代理层按 Key 统计;
  • 使用 Redis 访问代理或 APM 的命令采样;
  • 对业务关键路径增加 Key 维度的计数器;
  • 将热点 Key 与节点、槽、带宽和延迟关联分析。

7.3 redis-cli --hotkeys 的边界

某些 Redis CLI 版本提供:

redis-cli --hotkeys

它依赖 Redis 的 LFU 访问频率信息,通常要求使用 allkeys-lfuvolatile-lfu 等 LFU 策略。它并不是通过完整记录每次访问得到精确热度,而是利用 Redis 的近似 LFU 计数。

因此:

  • 没有启用 LFU 相关策略时,不能假定该命令能正常提供有意义的结果;
  • 结果是近似的;
  • 扫描会产生额外负载;
  • Cluster 环境需要针对各节点分别执行;
  • 热点可能随时间变化,单次扫描只代表某个观察时刻。

7.4 热点 Key 的拆分必须保持语义正确

假设一个全局计数器:

INCR page:view

为了分散写入,可以拆成:

page:view:0
page:view:1
...
page:view:15

写入时按用户 ID、请求 ID 或随机方式选择分片,读取时再聚合:

V=i=015ViV = \sum_{i=0}^{15} V_i

但这会改变原本 INCR page:view 的原子语义。若业务要求“读取值与最近一次写入严格一致”,聚合读取可能遇到并发更新窗口;若要求跨分片原子操作,还需要 Lua、事务或重新设计模型。

在 Redis Cluster 中还要考虑 Cluster 的多 Key 约束。使用事务或 Lua 操作多个 Key 时,相关 Key 通常必须位于同一哈希槽,可以通过 Hash Tag 设计:

counter:{page}:0
counter:{page}:1

不过 Hash Tag 会把相同 {page} 的 Key 放入同一个槽。如果目标是分散热点,就不能把所有分片设计成相同 Hash Tag,否则逻辑上分片了,物理上仍可能集中在一个槽。


八、复制监控:异步复制、偏移量和重同步

8.1 Redis 复制的基本状态

查看复制状态:

INFO replication

主节点可能看到:

role:master
connected_slaves:2
master_replid:...
master_repl_offset:123456789
repl_backlog_active:1
repl_backlog_size:1048576

从节点可能看到:

role:slave
master_host:10.0.0.10
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:123456700

不同 Redis 版本对字段名称和附加字段可能有所变化,应以当前版本输出为准。

核心概念:

  • 复制偏移量:主从双方处理复制流的位置;
  • 复制积压缓冲区:主节点保留最近一段复制数据,用于断线后的部分重同步;
  • 部分重同步:从节点短暂断线后,如果主节点仍保留所需数据,可以继续同步缺失部分;
  • 全量同步:无法部分同步时,主节点生成 RDB 或采用相应同步方式,从节点重新加载数据;
  • 复制延迟:从节点处理复制流落后于主节点的程度。

可以粗略比较:

Δoffset=master_repl_offsetslave_repl_offset\Delta_{\text{offset}} = \text{master\_repl\_offset} - \text{slave\_repl\_offset}

但这个差值不是严格的“秒数”。要估算时间延迟,还需要结合复制流速、网络传输和从节点处理能力。

8.2 复制为什么会引起延迟

一次写请求可能经历:

  1. 主节点执行写命令;
  2. 写入复制缓冲;
  3. 复制数据发送到从节点;
  4. 从节点读取并执行;
  5. 从节点回复确认。

当写入速率突然上升时,可能出现:

  • 主节点复制输出缓冲区增长;
  • 网络带宽被复制流量占用;
  • 从节点执行能力不足,偏移量持续落后;
  • 断线后复制积压缓冲不够,触发全量同步;
  • 全量同步期间产生 RDB、磁盘和网络压力;
  • fork 或写时复制造成主节点延迟尖峰。

检查客户端和复制缓冲信息:

INFO clients
INFO replication

重点关注客户端输出缓冲、从节点连接状态、同步进度和偏移量变化。单次采样不足以判断问题,应连续采样观察趋势。

8.3 WAIT 的语义与误解

WAIT 可以让当前客户端等待之前的写操作被指定数量的副本确认:

SET order:1001 paid
WAIT 1 100

含义通常是:等待至少一个副本确认,最多等待 100 毫秒。

但它不等于:

  • 副本已经把数据持久化到磁盘;
  • 主节点故障后一定不会丢失数据;
  • 复制变成同步复制;
  • 后续故障转移一定选择已经确认该写入的副本;
  • 跨地域网络下拥有确定的强一致性保证。

WAIT 的结果是确认数量,不应被当成布尔成功标志。若返回 0,说明在超时前没有副本确认;应用必须根据业务语义决定重试、降级或失败。

同时,等待副本会把复制延迟暴露给客户端,可能增加业务尾延迟。它适合对部分写入提供更强的复制确认语义,不适合无条件地应用于所有请求。


九、复制与故障诊断的完整路径

假设主节点写入延迟从 1 毫秒升高到 200 毫秒,同时从节点复制延迟不断增加。

可以按以下顺序推导:

1. 判断是不是从节点处理能力不足

连续查看:

INFO replication
INFO stats
INFO memory

如果主节点偏移量持续增长,而从节点偏移量增长较慢,可能是:

  • 从节点 CPU 不足;
  • 从节点执行了重查询或扫描;
  • 从节点磁盘、AOF 或 RDB 操作造成压力;
  • 网络带宽不足。

2. 判断是否发生全量同步

观察:

master_sync_in_progress
repl_backlog_active

如果正在同步,检查主节点是否产生了新的 RDB、网络流量是否骤增,以及从节点是否频繁断线。

3. 判断复制积压缓冲区是否过小

复制积压缓冲区能否支持部分重同步,与以下因素有关:

Tcoveragerepl_backlog_sizewrite_stream_rateT_{\text{coverage}} \approx \frac{\text{repl\_backlog\_size}} {\text{write\_stream\_rate}}

例如:

  • 积压缓冲区为 1 GB;
  • 主节点复制流平均为 20 MB/s;

理论覆盖时间约为 50 秒。若从节点断线超过这个时间,或者写入速率在故障期间上升,部分重同步可能失败。

这是估算,不是严格保证,因为复制流速会变化,而且还受实现和连接状态影响。

4. 检查主节点是否因持久化或 fork 受影响

INFO persistence
LATENCY LATEST

如果 fork 延迟与 RDB 保存、AOF 重写时间一致,应把问题归因到内存地址空间和后台持久化路径,而不是简单认为“复制慢”。


十、事件循环、I/O 线程与延迟的关系

Redis 的命令执行通常由主线程按顺序完成。Redis 可以使用 I/O 线程处理部分网络读写和协议解析工作,但这不等于命令执行被任意并行化。

因此以下命令仍可能阻塞其他命令:

KEYS *
FLUSHDB
SMEMBERS very-large-set
HGETALL very-large-hash
ZRANGE huge:zset 0 -1

I/O 线程能够减轻网络读写相关工作,但不能把一个复杂命令的核心数据结构遍历自动拆成多个并行任务。配置 I/O 线程前,应确认当前版本支持的范围、配置项和部署方式,并通过压测验证。

延迟的典型故障路径是:

一个高复杂度命令
    ↓
主线程长时间执行
    ↓
事件循环无法及时处理其他请求
    ↓
大量请求排队
    ↓
客户端 P99/P999 上升

这也解释了为什么“平均 CPU 不高”不能证明没有延迟问题。Redis 的主线程可能在短时间内被单个命令独占,而多核机器的总体 CPU 利用率仍不高。


十一、持久化、过期和阻塞命令的诊断边界

11.1 持久化造成的延迟

查看持久化状态:

INFO persistence

可关注:

  • RDB 是否正在保存;
  • AOF 是否正在重写;
  • 上次保存耗时;
  • AOF 写入和同步状态;
  • 最近一次失败信息。

持久化可能通过三条路径影响延迟:

  1. fork 创建子进程导致短暂停顿;
  2. 写时复制使主进程修改页面时产生额外内存和复制开销;
  3. 磁盘和 CPU 争用影响整个实例。

因此,应把 LATENCYINFO persistence、RSS 和系统磁盘指标放在同一个时间轴观察。

11.2 过期键并非一个独立的后台清理线程问题

Redis 处理过期键通常包括:

  • 访问 Key 时的惰性删除;
  • 定期主动过期扫描。

大量 Key 在同一时刻过期,可能使主动过期处理产生延迟尖峰。可观察:

INFO stats

关注 expired_keys 的增长速度,并结合 LATENCY 判断是否存在过期处理事件。

避免给海量 Key 设置完全相同的过期时间,通常是降低过期集中风险的一种数据建模手段,但仍应根据业务语义决定 TTL。

11.3 阻塞命令与连接池

BLPOPBRPOPXREAD BLOCK 等命令会让连接等待数据。等待时间不应直接解释为 Redis CPU 执行慢。

诊断时要区分:

  • 命令在等待业务事件;
  • 命令在 Redis 中执行很慢;
  • 客户端连接池没有可用连接;
  • 阻塞连接数量过多,影响其他资源。

查看客户端:

CLIENT LIST
INFO clients

如果大量连接处于阻塞状态,应检查消费者数量、超时时间、连接池设计和取消机制。不要把阻塞命令的业务等待时间直接作为 Redis 内核延迟。


十二、一个可执行的诊断脚本

下面示例使用 redis-cli 采集一组低侵入信息:

#!/usr/bin/env bash
set -euo pipefail

REDIS_CLI=(redis-cli -h 127.0.0.1 -p 6379)

echo "== server =="
"${REDIS_CLI[@]}" INFO server | grep -E '^(redis_version|uptime_in_seconds):'

echo "== memory =="
"${REDIS_CLI[@]}" INFO memory | grep -E \
'^(used_memory:|used_memory_rss:|used_memory_peak:|used_memory_dataset:|used_memory_overhead:|maxmemory:|maxmemory_policy:|mem_fragmentation_ratio:)'

echo "== stats =="
"${REDIS_CLI[@]}" INFO stats | grep -E \
'^(evicted_keys:|expired_keys:|keyspace_hits:|keyspace_misses:|instantaneous_ops_per_sec:)'

echo "== replication =="
"${REDIS_CLI[@]}" INFO replication | grep -E \
'^(role:|connected_slaves:|master_link_status:|master_sync_in_progress:|master_repl_offset:|repl_backlog_active:|repl_backlog_size:)'

echo "== commandstats =="
"${REDIS_CLI[@]}" INFO commandstats

echo "== slowlog =="
"${REDIS_CLI[@]}" SLOWLOG GET 20

echo "== latency latest =="
"${REDIS_CLI[@]}" LATENCY LATEST

这个脚本的边界是:

  • 它只做瞬时采样,不能替代时间序列监控;
  • INFO commandstats 的累计计数需要两个时间点做差,才能得到窗口内速率;
  • Slowlog 只展示超过阈值的命令;
  • 复制偏移量需要连续采样才能判断趋势;
  • 没有包含 MONITOR、全库扫描或大规模 Key 访问,因此相对适合定期执行。

例如,对两个采样点计算命令调用速率:

calls/sec=C2C1t2t1\text{calls/sec} = \frac{C_2-C_1}{t_2-t_1}

其中 C1,C2C_1,C_2 是两个时刻的 callst1,t2t_1,t_2 是采样时间。累计 usec 也可以类似计算,以估算窗口内命令执行时间占比。


十三、常见误判与修正方式

误判一:Slowlog 没有记录,所以 Redis 没有慢命令

修正:

  • Slowlog 只覆盖超过阈值的命令;
  • 客户端延迟还包括排队、网络和输出;
  • 检查 LATENCY LATESTINFO commandstats 和客户端时间线。

误判二:used_memory 没超过 maxmemory,所以不会 OOM

修正:

  • RSS、分配器保留内存、客户端缓冲和 fork 写时复制都可能扩大进程实际占用;
  • 检查 used_memory_rss、持久化状态、客户端缓冲和系统内存;
  • 容器环境还要检查 cgroup 限制。

误判三:复制偏移量接近,所以数据已经持久化安全

修正:

  • 复制偏移量描述复制流进度,不等同于磁盘持久化;
  • 异步复制仍可能在主节点故障窗口内丢失最近写入;
  • WAIT 是副本确认机制,不是通用的事务持久化保证。

误判四:使用 Cluster 后热点一定会自动分散

修正:

  • Cluster 按槽分布 Key;
  • 同一个热点 Key 仍然只有一个槽;
  • 多个热点 Key 可能恰好集中在同一个节点;
  • 需要同时观察 Key、槽和节点级流量。

误判五:把 SCAN 当作严格分页快照

修正:

  • SCAN 是增量遍历;
  • 数据变化期间可能返回重复项,也不保证快照一致性;
  • COUNT 是提示值;
  • 删除或修改 Key 的业务逻辑必须具备幂等性。

误判六:用 MONITOR 做长期热点统计

修正:

  • MONITOR 输出量大、可能暴露敏感信息,并增加额外负载;
  • 长期统计应在客户端、代理或采样系统中完成;
  • 需要精确热点分析时,应设计脱敏的 Key 维度指标。

十四、把指标组织成可解释的 SLO 诊断链

如果业务目标是 Redis 请求 P99 小于 20 毫秒,可以建立如下判断路径:

情况 A:客户端 P99 高,Slowlog 没有明显慢命令

优先检查:

  • 网络 RTT;
  • 客户端连接池;
  • Redis 事件循环延迟;
  • 是否存在其他命令造成排队;
  • 输出响应是否过大。

情况 B:Slowlog 出现大量大范围读取

优先检查:

  • Value 是否过大;
  • 是否错误使用全量读取;
  • 是否应该分页;
  • 是否需要拆分 Key;
  • 返回数据是否导致网络和客户端反序列化压力。

情况 C:fork 延迟与 P99 同时升高

优先检查:

  • RDB 保存或 AOF 重写;
  • Redis 地址空间和 RSS;
  • 写时复制造成的额外内存;
  • 磁盘和 CPU 争用;
  • 持久化策略是否与业务延迟目标匹配。

情况 D:主从偏移差距持续扩大

优先检查:

  • 从节点 CPU 和磁盘;
  • 网络带宽;
  • 复制输出缓冲;
  • 是否频繁全量同步;
  • 复制积压缓冲区是否覆盖短暂断线窗口。

情况 E:总体 QPS 不高,但某节点 P99 很高

优先检查:

  • Cluster 槽分布;
  • 热点 Key;
  • 大 Key 操作;
  • 某个客户端是否集中连接;
  • 单个节点是否承担了额外的复制或持久化任务。

Redis 监控的核心不是收集更多命令输出,而是把每个工具放回它实际测量的层次:

  • Slowlog 找单次执行过慢的命令;
  • Latency Monitor 找 Redis 内部延迟尖峰和阻塞路径;
  • INFO memoryMEMORY USAGE 和持久化指标解释内存压力;
  • INFO replication 判断复制进度、断线和重同步;
  • 用客户端采样、代理统计或受控工具识别热点 Key;
  • 最后用客户端端到端延迟验证这些内部信号是否真正解释了业务 SLO。

只有把“执行慢”“等待久”“传输慢”“内存紧张”“复制落后”和“访问集中”区分开,Redis 延迟诊断才不会停留在查看一张 Slowlog 表的层面。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。