Redis CPU不到20%,接口为什么还是成批超时?
2026/9/20 19:51:11 网站建设 项目流程

Redis CPU不到20%,接口为什么还是成批超时?

Redis CPU只有18%,内存使用率不到60%。

监控里没有明显慢命令,连接数也没有打满。

但应用每隔几分钟就会出现一批Redis超时。

看到这种现象,很多人的第一反应是:

Redis没满,问题应该不在Redis。

这次事故恰恰相反。

Redis执行命令确实不慢,真正慢的是一批体积很大的返回结果。命令执行完成后,数据还要经过Redis输出缓冲区、网络和Java客户端解码,应用才能拿到结果。

服务端CPU不高,只能证明CPU没有持续繁忙,不能证明整条调用链没有排队。


一、故障现场:所有指标都不像Redis有问题

事故发生时,商品查询接口出现周期性尖刺:

正常P99:70ms 异常P99:2.8s Redis客户端超时:2s Redis CPU:18% Redis内存:57%

应用日志里集中出现:

Redis command timed out Command timed out after 2 second(s)

我们先查了Redis慢日志:

redis-cli-h<host>-p<port>SLOWLOG GET20

没有找到对应时间点的慢命令。

再看吞吐、连接和阻塞客户端:

redis-cli-h<host>-p<port>INFO stats redis-cli-h<host>-p<port>INFO clients redis-cli-h<host>-p<port>INFO commandstats

QPS没有明显上涨,blocked_clients也接近0。

于是排查一度走偏:大家开始怀疑GC、线程池和网络抖动,却忽略了一个关键事实:

SLOWLOG记录的是命令执行时间,不包含把结果通过网络发送给客户端的时间。


二、慢日志为空,不等于Redis调用很快

一次Redis调用可以粗略拆成五段:

客户端排队 → 请求写入网络 → Redis执行命令 → 结果通过网络返回 → 客户端读取并解码

SLOWLOG主要覆盖中间的“执行命令”。

如果命令只执行了3ms,但返回了8MB数据,网络发送、客户端读取和反序列化可能远远超过3ms。

这也是为什么下面两个结论不能画等号:

SLOWLOG没有记录 ≠ 应用侧Redis耗时没有问题

Redis官方文档明确说明,慢日志的执行时间不包含客户端I/O。

因此,Redis超时时必须同时看两个视角:

  • 服务端执行了多久
  • 应用从发出请求到拿到结果用了多久

三、真正的根因:一个HGETALL返回了几MB

我们按超时时间点检查应用调用,最终定位到一个缓存读取:

Map<Object,Object>snapshot=redisTemplate.opsForHash().entries("product:snapshot:"+tenantId);

代码看起来只是一次Hash读取。

但某些租户把几万条商品快照全部塞进了同一个Hash。高峰时,这个Key已经增长到数十万个field,单次返回结果达到数MB。

问题随之出现:

  1. HGETALL需要遍历整个Hash
  2. Redis要为客户端准备大量返回数据
  3. 大响应进入客户端输出缓冲区
  4. Java客户端读取并解码大量对象
  5. Netty事件循环被大响应占用,其他请求跟着延迟

于是我们看到一种很迷惑的现象:

Redis整体CPU不高 单条命令未必进入慢日志 但一批应用请求同时超时

如果Redis是共享实例,一个大Key带来的长响应还可能拖慢其他完全无关的业务。


四、我会按这5组证据排查

1. 先确认超时发生在哪一段

不要只保留一句“Redis timeout”。至少区分:

获取客户端连接超时 连接Redis超时 写请求超时 等待响应超时 客户端解码耗时过长

同时对齐应用侧P95/P99、Redis命令名和Key类型。

2. 查SLOWLOG,但不要止步于SLOWLOG

redis-cli-h<host>-p<port>SLOWLOG LEN redis-cli-h<host>-p<port>SLOWLOG GET50redis-cli-h<host>-p<port>CONFIG GET slowlog-log-slower-than

生产环境修改阈值前要先评估权限、日志容量和变更流程,不要临时随手改完就忘记恢复。

3. 查命令分布和延迟事件

redis-cli-h<host>-p<port>INFO commandstats redis-cli-h<host>-p<port>INFO latencystats redis-cli-h<host>-p<port>LATENCY LATEST redis-cli-h<host>-p<port>LATENCY DOCTOR

latencystats等字段与Redis版本有关;没有该分区时先确认实例版本。LATENCY监控默认可能没有启用,需要根据业务可接受延迟设置阈值,并遵循生产变更流程。

4. 查客户端输出缓冲区

redis-cli-h<host>-p<port>CLIENT LIST

重点关注:

omem:客户端输出缓冲区占用 qbuf:查询缓冲区占用 cmd:最近执行的命令 idle:连接空闲时间

如果少数客户端的omem持续增大,通常说明响应生成速度超过了客户端消费速度。

CLIENT LIST可能包含地址、连接名等信息,保存和分享时要脱敏。

5. 查大Key和返回体积

不要在生产高峰直接运行可能造成全量扫描的命令。

可以先从业务Key规则、采样任务和只读副本入手,再使用渐进式工具核实:

redis-cli--bigkeysredis-cli--memkeys

这些工具会扫描Key空间,执行前应评估实例规模、链路负载和运行窗口。


五、为什么增加超时时间只是把问题藏起来?

把客户端超时从2秒改成5秒,可能暂时减少异常日志。

但等待中的请求会占用更多应用线程和连接,最终把上游也拖住:

Redis大响应 → 客户端等待时间变长 → Tomcat线程占用变长 → 请求开始排队 → Nginx或网关继续超时

超时时间应该根据业务SLA和下游能力设计,而不是发生超时后不断往上加。

如果问题是大Key和大响应,真正的修复仍然是减少一次调用的数据量。


六、最终怎么修?

我们没有继续提高超时,而是做了四件事。

1. 把一个大Hash拆分

由“一个租户一个大Hash”改成按业务维度和分页拆分,控制单个Key和单次返回规模。

2. 不再使用HGETALL读取全量

接口只返回当前页面需要的字段,批量读取也设置明确上限。

3. 给不同Redis操作建立独立指标

至少记录:

命令类型 调用次数 P95/P99 超时数 返回条目数或响应字节数

4. 给异常大响应设置保护

当查询范围超过上限时,直接分页、降级或拒绝,避免一个请求拖慢整个共享客户端。

修复后,异常租户的单次返回从数MB下降到几十KB,接口P99恢复到百毫秒以内,批量超时消失。


七、Redis批量超时排查清单

遇到“Redis不忙但应用超时”,我会按这个顺序检查:

  1. 对齐应用超时和Redis实例的时间线
  2. 区分连接、写入、响应和客户端解码超时
  3. 检查SLOWLOG阈值及最近记录
  4. 检查INFO commandstatslatencystats
  5. 检查LATENCY LATEST/DOCTOR
  6. 检查CLIENT LIST中的输出缓冲区
  7. 检查大Key、大响应和高复杂度命令
  8. 检查客户端事件循环、连接池和线程栈
  9. 检查网络丢包、重传和跨可用区链路
  10. 修复后用真实数据规模重新压测

写在最后

Redis CPU不到20%,不代表Redis调用一定很快。

慢日志为空,也不代表客户端在超时时间内一定能拿到结果。

排查Redis延迟时,别只盯着“命令执行了多久”,还要看“结果多大、网络传了多久、客户端处理了多久”。

这是「性能排障周」第2篇,归入「生产环境保命清单」

如果你的同事还在用“CPU不高,所以Redis没问题”下结论,可以把这篇转给他。

你遇到过Redis服务端不忙、应用却批量超时的情况吗?最后是大Key、网络、客户端,还是持久化抖动?欢迎把最终证据留在留言区。

系列导航

  • 上一篇:《接口只慢了500ms,为什么200个Tomcat线程还是被打满?》
  • 下一篇:《接口平均耗时只有80ms,用户为什么还是觉得卡?》

关注我,回复关键词保命,获取完整「生产环境保命清单」。

如需协助判断,可发送脱敏后的错误、指标、命令结果和时间线。请隐藏密码、Token、IP、域名及客户数据。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询