← 中文资源

简体中文版本

Redis 命令超时 — 区分慢命令与网络延迟

借助 SLOWLOG、延迟探测和 INFO 统计,区分 Redis 阻塞命令、fork 停顿、连接池排队与网络问题,再决定是否调整超时。

阅读英文原文

现象

客户端报 Timeout performing GET 或 Timeout reading from socket;单请求正常,一上并发就超时;应用 P99 抖动而 Redis CPU 不高;把客户端超时调大后错误暂时消失。

诊断

先在客户端机器测往返延迟并读慢日志:

redis-cli -h 127.0.0.1 -p 6379 --latency-history -i 5
redis-cli -h 127.0.0.1 -p 6379 SLOWLOG GET 10
redis-cli -h 127.0.0.1 -p 6379 INFO commandstats

延迟平稳且 SLOWLOG 为空,说明时间花在客户端连接池排队、大 value 序列化或网络链路上,而不是 Redis 计算。INFO commandstats 中 usec_per_call 偏高的 GET、HGETALL 通常意味着 value 过大。再查 INFO stats 的 evicted_keys、INFO memory 的 used_memory 与 maxmemory,以及 INFO persistence 的 latest_fork_usec:几百毫秒的 fork 停顿会让所有客户端同时超时。

根因

Redis 单线程执行命令,一条 KEYS、SORT 或大 key 操作会阻塞所有其他客户端,持久化 fork 也会造成停顿。若服务端延迟正常,时间则花在客户端池排队、序列化或网络重传上。

修复

用 SCAN 替代 KEYS,拆分大 hash 和 set,合并往返(pipeline),把重聚合移出 Redis。连接池按真实并发设置上限,命令超时要有界,并且只对幂等读做带抖动的重试:

const redis = new Redis({
  host: '127.0.0.1',
  port: 6379,
  commandTimeout: 500,
  maxRetriesPerRequest: 2, // 仅用于幂等读
  enableOfflineQueue: false,
});

不要对 INCR、LPUSH 等非幂等命令自动重试,超时并不代表命令没有执行。

验证

按生产负载压测,确认 SLOWLOG 无新增、--latency-history 平稳、evicted_keys 不再增长,应用 P99 明显低于配置的超时值,并持续观察一个完整流量周期。