上周值班的时候监控突然弹出 Redis 慢查询告警慢查询日志里全是HGETALL单条耗时经常冲到 80ms 以上。第一反应又是经典问题大 Hash 别用 HGETALL这个知识点群里都说烂了。一开始我也是这么处理的准备把这几个 HGETALL 改成 HMGET 分批拉。先把慢日志捞出来看记下对应的 key去线上实例查一下这个 hash 的 field 数量。结果有点意外这个 hash 里面也就三十多个 field体量很小按理说不应该这么慢。这下就有点懵。key 不大不是超大哈希没有热 key 打爆的迹象Redis 集群负载看着也平稳CPU、内存、网络带宽都没到水位线。那为什么 HGETALL 会持续进慢查询我先把客户端侧的耗时和服务端耗时拆开看。Redis 的 slowlog 记录的是服务端执行命令的时间不包含网络往返、客户端排队。但这次服务端记录的耗时本身就偏高排除单纯网络抖动。接着看实例的命令分布发现这个节点上定时任务很多大量在执行EXPIRE、KEYS历史遗留正在清理、还有持续的 RDB 落盘。刚好告警的时间段触发了 RDB。RDB fork 的时候主进程本身是阻塞的吗不是fork 是写时复制。但 fork 一瞬间操作系统要拷贝页表这个瞬间会有短暂停顿。如果刚好在这个停顿窗口里执行命令这条命令的耗时就会被拉长打进慢日志。而且 RDB 持久化期间内存持续有写入写时复制不断复制内存页额外带来 CPU 开销所有命令的响应都会跟着变缓不只是 HGETALL。我去核对了慢查询产生的时间点和 RDB 开始时间基本对上了。找到根因之后临时方案是调整 RDB 的触发条件把自动持久化的时间窗口挪到业务低峰同时降低持久化频率。调整完同一批 HGETALL耗时直接掉到 1~3ms慢查询告警消停了。本来以为事情到此为止隔了两天同一个实例又冒出慢查询这次是普通的 GET。没有 RDB也没有 AOF 重写。顺着链路追到客户端。项目用的是 Java 的 Lettuce连接池配置比较保守连接数给得很小。业务高峰期大量请求排队抢连接。慢日志里的服务端耗时明明很低但是业务监控上报的 Redis 耗时很高。这种就属于客户端排队阻塞命令在客户端连接池里等着送到 Redis 的时候已经晚了。这种情况很有迷惑性Redis 服务端没压力慢日志看着干净但是业务侧超时、耗时飙升。这两个问题分开之后我慢慢整理出来平时排查 Redis 慢查询容易被忽略的几类情况很多人上来只盯着命令本身第一类持久化带来的抖动RDB fork 瞬间停顿、AOF rewrite 刷盘。AOF 如果是 always 刷盘每一条命令都 fsync高写入场景下很容易慢everysec 相对友好但系统负载高的时候后台线程 fsync 阻塞也会传导到主线程。很多开发只知道大 key、热 key忘了持久化本身就是抖动来源。第二类操作系统层面的限制内存交换 swap。一旦 Redis 内存被挤到 swap 分区本来内存访问的操作变成磁盘 IO随便一条简单 GET 都能几百毫秒。这个非常致命而且容易漏看很多监控面板默认不展示 swap 使用情况。现在我们这边 Redis 节点一律关闭 swap。第三类客户端侧排队、序列化耗时。比如连接池不够、命令堆积、客户端同步阻塞等待、对象序列化 / 反序列化耗时算到 Redis 调用耗时里。这部分不会出现在 Redis 的 slowlog只能埋点在调用前后打时间戳拆分。第四类集群内的迁移、槽位迁移。Redis Cluster 做 rebalance、slot 迁移的时候迁移中的 key 访问会被阻塞短时间内出现一批随机的慢命令。第五类本身命令确实不合适就是大家最熟悉的HGETALL、LRANGE 取超大范围、KEYS、FLUSHDB 这类。这个是最容易识别但有时候不是慢查询的真正元凶只是刚好在抖动期间被记录下来。再说说我自己现在排查的顺序不再上来就改命令先区分是 Redis slowlog 里面的服务端耗时高还是业务埋点看到的总耗时高看同一时间有没有持久化动作RDB / AOF rewrite检查节点 swap、CPU 上下文切换、磁盘 IO确认是不是 slot 迁移、主从同步在全量同步再看 key 本身大小分析命令是否合理最后看客户端连接池、并发排队情况。回到这次的 HGETALL虽然这次慢的根源是 RDB但我后面还是慢慢改掉了这一批 HGETALL。理由很简单它本身依然是不稳定的如果以后这个哈希慢慢膨胀或者实例再次抖动它会是最先爆出来的那个。优化命令和排查抖动两件事不冲突。Redis 主线程是单线程串行执行命令这个基础大家都懂。但很多人理解的 “慢命令”只是命令计算本身慢。实际上主线程任何形式的停顿都会让所有正在执行的命令一起遭殃。一条看起来无害的命令出现在慢日志它可能只是那个倒霉的、刚好撞上停顿的受害者而不是故障本身。