现象
客户端报 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 明显低于配置的超时值,并持续观察一个完整流量周期。