数据库基础体系 · 第 123/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
Redis 监控与延迟诊断:Slowlog、Latency、内存、复制和热点
Redis 的性能问题通常不会直接表现为“某条命令很慢”。客户端看到的延迟,可能来自命令排队、Redis 主线程执行、网络传输、事件循环被阻塞、内存分配、复制缓冲区、持久化子进程创建,或者热点数据导致的资源争用。
因此,诊断 Redis 延迟时需要先区分几个概念:
- Slowlog:记录执行时间超过阈值的命令。
- Latency Monitor:记录 Redis 内部特定事件的延迟尖峰。
- 客户端延迟:从客户端发出请求到收到响应的完整时间。
- 内存诊断:判断是否发生淘汰、分配器膨胀、大 Key 或碎片问题。
- 复制诊断:判断主从同步、复制缓冲和重同步是否造成压力。
- 热点诊断:判断是否少数 Key、命令、分片或客户端集中消耗资源。
这些工具观察的是不同时间区间和不同层次。不能用 Slowlog 单独解释所有延迟,也不能看到 used_memory 正常就断定 Redis 没有内存问题。
一、先建立延迟模型:客户端看到的时间由什么组成
设客户端一次请求的端到端延迟为:
其中:
- :请求进入 Redis 后,在事件循环或其他队列中等待的时间;
- :Redis 执行命令的时间;
- :生成响应、写入输出缓冲区所需的时间;
- :响应通过网络传输的时间;
- 最后一个 :客户端线程调度、连接池排队、反序列化等客户端开销。
Slowlog 主要观察 ,而不是完整的 。
例如:
- 客户端发送一个
GET; - Redis 已经被另一个
KEYS *长时间占用; GET在事件循环中等待;GET真正执行只需要几十微秒;- 客户端仍然可能观察到数百毫秒延迟。
此时:
- 客户端监控会看到一次高延迟;
- 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"
一条记录通常包含:
- Slowlog ID;
- Unix 时间戳;
- 执行耗时,单位微秒;
- 命令及其参数;
- 客户端地址;
- 客户端名称,如果设置过。
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不宜无限增大。
一个合理的诊断过程不是永久把阈值设为极低,而是:
- 先按业务 SLO 设定观察阈值;
- 读取多个时间窗口的 Slowlog;
- 统计命令类型、参数规模、客户端来源;
- 结合客户端延迟和
LATENCY判断是否同一时间发生; - 诊断结束后恢复合适阈值。
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:平均每次执行微秒数。
可以用一个简单模型估计某类命令的累计执行贡献:
其中:
- 是调用次数;
- 是平均执行时间;
- 是这类命令累计占用的执行时间。
这个模型不等于精确 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 为什么要同时看 command 和 fork
考虑一次 RDB 保存:
- Redis 主进程调用
fork创建子进程; - 操作系统复制页表;
- 子进程遍历内存并写入 RDB;
- 主进程继续接收命令;
- 主进程对被修改的内存页执行写时复制。
如果 Redis 使用了很大的地址空间,fork 本身可能造成一次明显的主线程停顿。此时:
- Slowlog 可能记录被延迟执行的业务命令,也可能不记录;
- Latency Monitor 可能记录
fork或事件循环相关延迟; - 客户端会看到多个命令同时变慢;
- CPU、内存带宽和 RSS 可能在保存期间升高。
这与一条 HGETALL 执行过慢的处理方式不同。前者应检查持久化和内存规模,后者应检查数据结构大小和访问方式。
3.3 Latency Monitor 的边界
Latency Monitor 记录的是 Redis 内部已定义的事件,不是完整的分布式追踪系统。它通常不能告诉你:
- 哪个业务请求的 Trace ID;
- 哪个上游服务等待了多久;
- 完整的网络往返分解;
- 某个 Key 在全局访问中的精确占比。
因此,生产环境通常采用三层观测:
- 客户端埋点或代理层:记录端到端延迟;
- Redis Slowlog 与
INFO commandstats:记录命令维度; - 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 毫秒。
推导过程是:
- Redis 事件循环在同一执行路径上处理命令;
KEYS *需要遍历整个 Key 空间;- 在它执行期间,其他命令无法正常获得执行机会;
GET只能等待;- 客户端看到的是等待时间加上
GET执行时间; - 因而
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 内部已用内存的比值,通常近似为:
这个比值只能作为信号,不能机械地把某个数值直接判定为故障。分配器、操作系统、后台 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 持续增长,说明已达到内存管理边界并发生淘汰。若业务依赖缓存命中率,应同时观察:
当分母不为零时,这个指标可以粗略表示 Key 查询命中率。但它不能区分业务接口,也不能解释为什么某类 Key 被淘汰。
5.4 淘汰策略的语义
常见策略包括:
noeviction:达到限制后,写入命令通常返回内存不足错误;allkeys-lru:从所有 Key 中选择近似 LRU 的 Key 淘汰;volatile-lru:只从设置了过期时间的 Key 中选择;allkeys-lfu、volatile-lfu:按近似 LFU 频率选择;allkeys-random、volatile-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 成为分布式锁、计数器或队列的竞争点;
- 客户端连接集中到一个节点。
需要区分三种热点:
- 命令热点:例如
GET调用总量极高; - Key 热点:大量请求集中访问同一个 Key;
- 分片热点:多个热点 Key 落在同一个 Cluster 节点或槽范围。
INFO commandstats 能发现命令热点,但不能直接告诉你具体 Key:
INFO commandstats
SLOWLOG 能看到慢命令的参数,但只覆盖超过阈值的命令,不能统计所有访问频率。
7.2 使用 MONITOR 的风险
MONITOR
会持续输出 Redis 处理的命令。它适合短时间、低流量、隔离环境下确认某个 Key 是否被访问,但不适合作为长期监控手段。
原因包括:
- 每条命令都要额外格式化并发送;
- 输出量可能非常大;
- 可能暴露业务参数和敏感数据;
- 可能增加 Redis 和监控客户端的负担。
使用时应:
- 只授权给必要人员;
- 尽量限制观察时间;
- 不把输出长期写入日志;
- 注意命令参数中的 Token、手机号、用户标识等敏感信息;
- 在 Cluster 中分别观察实际承载请求的节点。
更适合生产环境的方式是:
- 在客户端中对请求做采样,记录命令类型和脱敏后的 Key;
- 在代理层按 Key 统计;
- 使用 Redis 访问代理或 APM 的命令采样;
- 对业务关键路径增加 Key 维度的计数器;
- 将热点 Key 与节点、槽、带宽和延迟关联分析。
7.3 redis-cli --hotkeys 的边界
某些 Redis CLI 版本提供:
redis-cli --hotkeys
它依赖 Redis 的 LFU 访问频率信息,通常要求使用 allkeys-lfu 或 volatile-lfu 等 LFU 策略。它并不是通过完整记录每次访问得到精确热度,而是利用 Redis 的近似 LFU 计数。
因此:
- 没有启用 LFU 相关策略时,不能假定该命令能正常提供有意义的结果;
- 结果是近似的;
- 扫描会产生额外负载;
- Cluster 环境需要针对各节点分别执行;
- 热点可能随时间变化,单次扫描只代表某个观察时刻。
7.4 热点 Key 的拆分必须保持语义正确
假设一个全局计数器:
INCR page:view
为了分散写入,可以拆成:
page:view:0
page:view:1
...
page:view:15
写入时按用户 ID、请求 ID 或随机方式选择分片,读取时再聚合:
但这会改变原本 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 或采用相应同步方式,从节点重新加载数据;
- 复制延迟:从节点处理复制流落后于主节点的程度。
可以粗略比较:
但这个差值不是严格的“秒数”。要估算时间延迟,还需要结合复制流速、网络传输和从节点处理能力。
8.2 复制为什么会引起延迟
一次写请求可能经历:
- 主节点执行写命令;
- 写入复制缓冲;
- 复制数据发送到从节点;
- 从节点读取并执行;
- 从节点回复确认。
当写入速率突然上升时,可能出现:
- 主节点复制输出缓冲区增长;
- 网络带宽被复制流量占用;
- 从节点执行能力不足,偏移量持续落后;
- 断线后复制积压缓冲不够,触发全量同步;
- 全量同步期间产生 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. 判断复制积压缓冲区是否过小
复制积压缓冲区能否支持部分重同步,与以下因素有关:
例如:
- 积压缓冲区为 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 写入和同步状态;
- 最近一次失败信息。
持久化可能通过三条路径影响延迟:
fork创建子进程导致短暂停顿;- 写时复制使主进程修改页面时产生额外内存和复制开销;
- 磁盘和 CPU 争用影响整个实例。
因此,应把 LATENCY、INFO persistence、RSS 和系统磁盘指标放在同一个时间轴观察。
11.2 过期键并非一个独立的后台清理线程问题
Redis 处理过期键通常包括:
- 访问 Key 时的惰性删除;
- 定期主动过期扫描。
大量 Key 在同一时刻过期,可能使主动过期处理产生延迟尖峰。可观察:
INFO stats
关注 expired_keys 的增长速度,并结合 LATENCY 判断是否存在过期处理事件。
避免给海量 Key 设置完全相同的过期时间,通常是降低过期集中风险的一种数据建模手段,但仍应根据业务语义决定 TTL。
11.3 阻塞命令与连接池
BLPOP、BRPOP、XREAD 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, 是采样时间。累计 usec 也可以类似计算,以估算窗口内命令执行时间占比。
十三、常见误判与修正方式
误判一:Slowlog 没有记录,所以 Redis 没有慢命令
修正:
- Slowlog 只覆盖超过阈值的命令;
- 客户端延迟还包括排队、网络和输出;
- 检查
LATENCY LATEST、INFO 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 memory、
MEMORY USAGE和持久化指标解释内存压力; - 用 INFO replication 判断复制进度、断线和重同步;
- 用客户端采样、代理统计或受控工具识别热点 Key;
- 最后用客户端端到端延迟验证这些内部信号是否真正解释了业务 SLO。
只有把“执行慢”“等待久”“传输慢”“内存紧张”“复制落后”和“访问集中”区分开,Redis 延迟诊断才不会停留在查看一张 Slowlog 表的层面。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:Redis Search 与向量检索:索引、查询、Hybrid 和容量边界
- 下一篇:Lucene 与倒排索引内部:Segment、Term、Posting、Merge 和 Cache
- 延伸:Redis 内核与事件循环:命令执行、IO 线程、阻塞点和延迟
- 延伸:数据库可观测性:连接、事务、锁、执行计划、复制和 SLO
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论