一次促销配置热 Key 打满 Redis Cluster 单分片的复盘(匿名化合成案例)。
某业务集群使用 Redis Cluster 缓存活动配置。活动开始后,部分接口的 p99 延迟从几十毫秒升到数秒,并出现连接超时和重试。Redis 集群总 CPU 看起来只有三成左右,但其中一个主节点的单核 CPU 已接近 100%;同一分片上的其他 key 也开始变慢,其他分片仍然平稳。内存、网络和集群状态没有明显异常。
--bigkeys 没有发现异常,SLOWLOG 也几乎是空的,但请求仍在排队。CLUSTER KEYSLOT 确认该 key 落在过热分片。受影响的不只是活动配置接口。哈希槽落在该分片上的其他 key 也共享同一个事件循环,因此一些无关业务出现了延迟升高和超时。客户端重试又放大了流量,形成短时拥塞。没有数据丢失或主从切换,但部分请求在 HTTP 层返回超时,用户侧表现为页面加载慢或重试。
第一个误导信号是集群聚合 CPU。整体只有三成,很容易让人认为 Redis 还有大量余量,于是转向排查网络和客户端。真正有意义的指标是每个主节点的 CPU、事件循环延迟和该节点上的命令耗时;一个 Cluster 有多个分片,不代表负载会自动均摊,一个 key 永远只属于一个哈希槽和一个主节点。
第二个误导信号是空的 SLOWLOG。慢日志记录的是命令执行时间,不包含命令在 socket 和事件循环队列里等待的时间。单次 GET 本身很快,但当分片被打满后,请求会先在队列中排队,客户端看到的是整体延迟和超时,慢日志却可能没有任何记录。MEMORY USAGE 也证明这个 key 并不大,推翻了“大 key 导致阻塞”的假设。最后是应用维度和 CLUSTER KEYSLOT 的证据把问题锁定到单一热 key。
活动配置是一个读多写少、访问极度集中的 key。它所在的槽位只能由一个主节点处理,而 Redis 执行命令的主线程是单线程模型,单核被打满后,同一分片上的所有请求都要排队。配置统一过期、应用没有本地缓存和 singleflight,导致大量并发 miss 同时回源并回填;活动流量和客户端重试叠加,最终把一个普通小 key 变成了热点。
这不是“集群容量不足”的平均问题,也不是大 key 或慢命令问题。增加分片数量不会自动拆散一个 key,重新分片也只能迁移整个槽位,仍然只有一个主节点承接该 key 的读写。
先降低放大效应,再处理 key 的分布。确认该配置短暂陈旧可接受后,在应用侧增加带随机抖动的短 TTL 本地缓存,并对同 key 的 miss 做请求合并,避免所有实例同时回源。给客户端设置明确超时、限制重试次数并加入退避和抖动,防止重试风暴继续打满分片。
定位和验证命令如下:
redis-cli --cluster call <host>:<port> INFO cpu
redis-cli --cluster call <host>:<port> INFO commandstats
redis-cli --cluster check <host>:<port>
redis-cli -c -h <host> -p <port> CLUSTER KEYSLOT <key>
redis-cli -c -h <host> -p <port> CLUSTER NODES
redis-cli -c -h <host> -p <port> SLOWLOG GET 20
redis-cli -h <host> -p <port> CONFIG GET maxmemory-policy
只有确认淘汰策略为 LFU 时,才在对应主节点上使用 redis-cli --hotkeys;它会扫描 key,本身也有开销,不要在高负载时反复执行。MONITOR 更不适合直接开在高 QPS 主节点上,必须采样时应在低峰、限时并设置超时,优先使用应用侧埋点和代理日志。
长期修复根据读写性质选择:
SLOWLOG、LATENCY、命令统计和 key 分布,但不要用慢日志为空来排除 Redis 侧排队。合上页面,先回答,再展开对照。
GET 仍可能很快执行,但客户端早已在排队和超时。
小程序版
微信扫码,随时查命令