Redis超时排查实战:从网络链路到慢查询大Key的完整指南

📅 发布时间:2026/10/6 22:52:11
Redis超时排查实战:从网络链路到慢查询大Key的完整指南
“Redis超时”这四个字做后端的基本都见过。尤其在云服务器上部署Redis实例客户端突然报timeout日志一片红业务侧请求开始堆积紧接着告警群就炸了。我在HoRain云上维护过好几套Redis集群从单节点到主从再到哨兵模式超时这个问题前前后后遇过十几次排查手段也从一开始的乱抓瞎慢慢沉淀成了一套固定流程。这篇东西不是教科书是我把实际排查Redis超时的经验整理出来的一份可落地的排查手册。里面包含网络链路怎么验证、服务端怎么查慢查询和大Key、客户端连接池和超时参数怎么调、以及一个完整的线上案例复盘。适合正在被Redis超时折磨的运维和后端开发也适合刚接手Redis集群、想建立排查思路的同学当作备忘录。1. Redis超时到底超在哪里先把问题分类定位很多人在排查超时的时候第一步就错了——拿着报错日志直接上服务器看Redis状态看到一切正常就开始怀疑云厂商。实际上Redis超时是一个链路问题从客户端发起命令到收到响应中间要经过客户端网络栈、物理网络、云平台网络组件、服务端内核协议栈、Redis自身事件循环任何一环出问题都可能表现为“超时”。所以第一步不是急着修而是先定位超时发生在哪个环节。1.1 三种超时表现对应三类根因以Java生态为例最常见的超时异常有三类每一类指向的排查方向完全不同。第一类是连接超时报错类似connect timed out或者java.net.SocketTimeoutException: connect timed out。意思是TCP三次握手都没有完成客户端压根没连上Redis服务端。这类问题优先查网络连通性、安全组规则、服务端口是否监听、以及服务端连接数是否已经打满。第二类是读取超时报错类似Read timed out或Lettuce的RedisCommandTimeoutException: Command timed out after X milliseconds。握手没问题连接也建立了但命令发出去之后在设定的时间内没等到响应。这种情况大概率是Redis服务端处理不过来或者某个慢命令阻塞了事件循环也有可能是网络链路中间出现了丢包和延迟抖动。第三类是连接池获取超时报错类似Could not get a resource from the pool或JedisExhaustedPoolException。请求在客户端本地就失败了根本没发到Redis。这说明连接池里的连接全被占用或者连接池本身配置太小。这类问题先看连接池参数再看是不是存在连接泄漏。区分这三类报错是排查工作的第一准则。我见过有人拿着读超时的日志去改安全组折腾半天毫无效果根源其实是客户端连接池耗尽方向错了整个排查就成了无底洞。1.2 排查前先回答五个问题在动手敲命令之前我习惯先回答下面五个问题答案能直接决定排查路径第一超时是偶发还是持续偶发多跟慢查询、fork阻塞、网络抖动有关持续超时多跟连接数打满、安全组阻断、服务端宕机有关。第二超时发生在哪个时间点如果每天固定时间出现大概率跟定时任务、RDB持久化、AOF重写有关。第三最近有没有变更Redis版本升级、客户端依赖升级、安全组策略调整、服务器迁移这些变更往往是超时的直接导火索。第四超时的客户端分布在哪里如果只有某一台业务服务器超时而其他机器正常问题大概率在那台机器或它到Redis之间的网络路径上如果所有客户端都超时那就盯服务端。第五Redis的部署形态是什么单节点、主从、哨兵还是集群如果是集群还要确认客户端访问的是哪个节点访问Slot对应的节点是否发生了迁移。这五个问题花不了两分钟但能把排查范围从“全链路”缩小到“某一层”。我自己后来所有超时排查都从这一步开始效率提升非常明显。2. 网络链路排查云环境里超时的第一嫌疑在云环境里排查Redis超时网络链路永远是第一嫌疑。因为服务端CPU、内存都正常、慢查询也没有但客户端就是超时这种情况我遇到太多次了。云环境的网络路径比物理机复杂中间多了安全组、网络ACL、虚拟交换机、NAT网关这些组件任一层配置不对TCP连接就会表现成超时或者异常重传。2.1 连通性与延迟实测从ping到redis-cli --latency先从最简单的连通性测起。在出问题的业务服务器上执行ping Redis服务端IP看丢包率和延迟。但要注意很多云环境默认禁ping或者不响应ICMPping不通不代表端口不通只能作为参考。接着测端口连通性用telnet RedisIP 6379能连上就说明TCP层没问题。更推荐用nc -vz RedisIP 6379脚本化的时候更方便。如果telnet卡住不动那就是网络层到端口这一段有问题接下来查安全组、防火墙、路由表。端口通之后测真实延迟。最直观的工具是redis-cli自带的延迟测试命令redis-cli -h RedisIP -p 6379 --latency这个命令会持续发送PING统计最小、最大、平均延迟。正常情况下内网Redis延迟应该在1毫秒以内如果平均值飙升到几十毫秒甚至更高那网络链路或服务端事件循环一定有瓶颈。想观察延迟随时间的变化用带历史记录的版本redis-cli -h RedisIP -p 6379 --latency-history它会每隔一段时间打印一次延迟统计能看出延迟是持续高还是周期性抖动。周期性抖动往往跟服务端定时任务有关比如RDB快照或者AOF刷盘。如果--latency正常但业务还是超时那就抓包看看。在业务服务器上用tcpdump抓Redis端口的流量重点看TCP重传率tcpdump -i eth0 host RedisIP and port 6379 -w /tmp/redis.pcap导出后用Wireshark打开过滤tcp.analysis.retransmission如果重传包很多说明链路上存在丢包可能是带宽跑满或者云网络组件限速。2.2 安全组、防火墙与带宽限速云上特有的坑云环境里有一类超时特别坑安全组规则配置错误导致的“半通”状态。TCP握手能完成但数据传输阶段被丢弃表现就是偶尔能成功、偶尔超时。排查方法很简单在云控制台检查Redis实例所在安全组的入方向规则确认放行了客户端IP和端口。这里有个容易被忽视的细节如果业务服务器和Redis不在同一个VPC是通过对等连接或者公网访问的那还要检查路由表、网络ACL和对端安全组。我遇到过跨VPC访问Redis偶发超时最后发现是网络ACL里一条规则顺序写反了放行规则落在了拒绝规则后面导致部分流量被默认拒绝。带宽限速也是云上特有的超时来源。很多云主机默认带宽是共享的如果同一台服务器上还有其他业务在跑大流量下载Redis的包就会在虚拟网卡队列里排队。这种场景下ping延迟和--latency都会异常但服务器本身的CPU、内存看起来完全正常。另外注意云主机的CPU steal指标。在共享型实例上物理机的其他虚拟机抢占CPU会导致Redis服务端事件循环被卡客户端表现为偶发超时。在服务器上执行top观察%st这一列如果持续大于5%说明CPU资源被抢占这时唯一的解法是升级实例规格或者迁移到独享型实例。2.3 连接数打满一个端口能承载的连接是有限的Redis的连接数上限由maxclients配置控制默认是10000。云上很多默认配置没调过如果业务并发高、客户端连接池又没做回收很容易把连接数打满。连接数打满的典型表现是新的连接失败日志里出现ERR max number of clients reached但已经建立的连接仍然正常工作所以看起来像是“部分请求超时”。在Redis服务端执行INFO clients看connected_clients是否接近上限redis-cli INFO clients如果接近上限先临时调大maxclients救急再排查为什么会积攒这么多连接。用ss -s看服务器当前的TCP连接状态如果TIME_WAIT数量巨大说明客户端频繁创建短连接没有复用连接池。这也是一些老项目的通病——每次请求都新建Redis连接请求结束也不关闭导致大量连接堆积。解决短连接问题的核心是用连接池复用连接。另外云主机内核里有一个参数值得检查sysctl net.core.somaxconn这个参数影响TCP全连接队列的长度如果太小高并发下客户端连接会积压在内核队列里表现为连接超时。一般建议调整到1024以上同时Redis配置里的tcp-backlog也要对应调大。3. 服务端性能排查慢查询、大Key与持久化阻塞网络排查完之后如果链路没有明显问题下一个重点就是Redis服务端自身。Redis是单线程事件循环模型所有命令在一个线程里排队执行。这意味着只要有一条命令执行很慢后面所有命令都得等客户端自然大面积超时。这也是为什么服务端排查的核心就三个词慢命令、大Key、fork阻塞。3.1 用SLOWLOG定位耗时命令Redis自带的慢查询日志是定位超时元凶的第一工具。先看当前的慢日志阈值redis-cli CONFIG GET slowlog-log-slower-than默认是10000微秒也就是10毫秒。在超时场景下这个阈值太宽了一条命令耗10毫秒才记录很多真正的元凶会漏掉。建议调低到1000微秒也就是1毫秒redis-cli CONFIG SET slowlog-log-slower-than 1000然后拉取最近的慢日志redis-cli SLOWLOG GET 50输出里能看到每条慢命令的执行时间、耗时、客户端IP等信息。重点看有没有KEYS、SMEMBERS、HGETALL、ZRANGE这类命令它们的时间复杂度是O(N)一旦集合里有几十万个元素执行时间直接上百毫秒。这里必须强调一个高频踩坑点生产环境绝对不要用KEYS *去匹配键。KEYS会遍历整个键空间Redis单线程执行期间所有读写全部阻塞。我见过有人为了查一个键执行了KEYS *user*结果Redis阻塞了2秒线上超时告警直接刷屏。正确的做法是用SCAN命令分批迭代或者直接把键名设计成可预测的前缀模式从业务层面避免全量扫描。3.2 大Key与热Key怎么引发超时慢日志只能记录已经发生的慢命令但大Key往往是慢命令的根源。所谓大Key就是一个键对应的value非常大比如一个Hash里有上百万个字段一个List里有几百万个元素或者一个String类型的value有几十兆。大Key的杀伤力在于很多看似无害的命令作用在大Key上就会变成灾难。比如HGETALL一个百万字段的Hash执行时间可能超过1秒DEL一个几百万元素的List同样会阻塞Redis很久。此外大Key在被删除、被序列化、被网络传输时都会带来巨大的开销。用redis-cli自带的扫描工具可以快速找出大Keyredis-cli --bigkeys它会在线上用SCAN迭代所有键找出每种数据类型里最大的几个键。注意这个命令本身也会有一些开销建议在业务低峰期执行最好配合--i 0.1参数控制扫描间隔避免影响线上。热Key则是另一个维度的问题。某个键被超高频率访问导致单个Redis实例的部分CPU被打满或者触发大量的网络IO。判断热Key的方式可以直接看INFO commandstats里的命令调用次数也可以临时用MONITOR观察一段时间。但MONITOR在生产环境要慎用它会输出所有命令本身就有性能损耗我一般只会在低峰期开几秒种。应对大Key思路是拆分。一个大Hash拆成多个小Hash按业务维度分片大List改成多个小List或者改用Stream大String改成压缩存储。热Key的应对思路是加本地缓存把高频访问的键在业务进程内缓存几十秒分流对Redis的冲击。3.3 持久化与forkRedis的“卡顿瞬间”还有一种超时特别迷惑平时一切正常每天固定时间点出现几十秒的延迟毛刺。这种我闭着眼睛都知道八成是持久化配置的问题。Redis默认开启RDB持久化bgsave会fork一个子进程来生成快照。fork本身是阻塞的数据集越大fork耗时越长。一个10GB内存的实例fork可能耗时几百毫秒到几秒期间Redis无法处理任何命令所有客户端都会超时。查看最近的fork耗时redis-cli INFO stats | grep latest_fork_useclatest_fork_usec单位是微秒如果这个值持续在几十万甚至上百万说明数据集过大fork已经成了性能瓶颈。AOF重写也有类似问题。当AOF文件增长到触发重写条件时Redis会fork子进程进行重写同样会造成阻塞。如果开启了AOF检查aof_rewrite相关的日志确认重写时间是否跟超时时间段吻合。另外内存碎片整理和交换分区也会造成卡顿。用INFO memory查看mem_fragmentation_ratio如果这个值大于1.5说明内存碎片严重Redis在做内存分配时会变慢。再看操作系统层面有没有用了swap执行cat /proc/[redis_pid]/smaps如果Swap字段不是0说明Redis内存被换出到磁盘了这种情况下任何命令都可能超时。还想提醒一个内核参数透明大页THP。Redis官方文档明确建议关闭THP因为它会导致内存页在写时复制时触发巨大的延迟。确认一下当前的设置cat /sys/kernel/mm/transparent_hugepage/enabled如果输出是always建议改成never改完Redis在fork时的阻塞会明显好转。4. 客户端侧调优超时参数与连接池的正确打开方式服务端查了一圈没发现明显问题这时候该回头看看客户端了。很多Redis超时其实根子在客户端超时参数设得太激进连接池配置太小序列化方式太慢命令批量操作没做优化。这些细节单个看都不起眼叠加起来就是生产事故。4.1 超时参数connectTimeout、socketTimeout与commandTimeout怎么设不同Redis客户端的超时参数名称不一样但逻辑是一样的。以Java生态里最常用的几个库为例Jedis里连接超时和读写超时是同一个参数在构造JedisPool时传入JedisPoolConfig poolConfig new JedisPoolConfig(); int connectTimeout 2000; // 连接超时单位毫秒 int readTimeout 2000; // 读写超时单位毫秒 JedisPool pool new JedisPool(poolConfig, redis.horain.com, 6379, connectTimeout, readTimeout);Lettuce里有两个维度连接超时和命令超时RedisClient client RedisClient.create(redis://10.0.0.5:6379); client.setOptions(ClientOptions.builder() .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(2)).build()) .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(2))) .build());超时参数的设置原则是不要太短也不要太长。太短了Redis正常慢一点点比如主从切换、AOF重写期间的短暂阻塞就被判定成超时太长了一旦Redis真的挂了请求会全部堆积在客户端占满线程池拖垮整个应用。我一般建议连接超时设1到2秒读写超时设2到3秒。如果业务对延迟有严格要求可以再收紧到1秒但要结合服务端的慢查询分布来看确保绝大多数命令能在几百毫秒内返回。顺便说一个反直觉的点有些项目把超时设成-1意思是无限等待。这绝对是个定时炸弹。Redis万一挂掉所有请求都挂在那边不返回线程池耗尽进程彻底卡死。宁可超时后快速失败并重试也不要无限等。4.2 连接池参数从报错反推配置连接池参数是客户端调优的重头戏。先看一个经典的报错链业务高峰期日志里全是Could not get a resource from the pool这就是连接池被掏空了。连接池的核心参数有四个最大连接数maxTotal、最大空闲连接数maxIdle、最小空闲连接数minIdle、获取连接的最大等待时间maxWaitMillis。maxTotal怎么估算一个粗略的公式是预估QPS乘以单次命令平均耗时再乘以一个冗余系数。比如Redis的QPS是2000平均耗时5毫秒那么并发需要的连接数大约是2000 × 0.005 10个考虑到毛刺和网络波动配置到20到30个比较稳妥。如果业务上使用了Pipeline批量操作那并发连接数可以更少。再强调两个容易被忽略的参数。第一是maxWaitMillis它控制获取连接的等待上限我习惯设1000到2000毫秒超过这个时间直接抛异常避免请求在连接池里无限排队。第二是连接有效性检测不建议开启testOnBorrow因为每次借连接都做一次PING在高并发下会白白消耗Redis的性能。更推荐的是testWhileIdle配合空闲连接检测每隔一段时间对空闲连接做保活探测既能清理失效连接又不会影响每次请求的路径。如果设置了合理的连接池参数仍然报池耗尽那就要查连接泄漏了。检查代码里是否有获取连接后没归还的情况比如在finally块里忘了调用close()或者returnBrokenResource用错了地方。我排查过一个项目连接池明明有50个连接跑一个小时后就开始报超时最后发现是一个异常分支里没有归还连接每次调用泄漏一个到高峰期直接把池子吃光。4.3 Pipeline与序列化降低单次操作耗时的手段客户端侧的另一个优化方向是减少命令的往返次数。如果业务需要批量写入几百个键用循环挨个SET每次都是一次RTT。压力小时没问题并发一起来网络往返时间就会放大延迟。改成Pipeline后几百条命令一次网络包发过去一次拿回结果Redis服务端处理的总耗时反而因为批量执行而大幅下降。Pipeline pipeline jedis.pipelined(); for (int i 0; i 1000; i) { pipeline.set(key: i, String.valueOf(i)); } pipeline.sync();序列化方式同样影响超时。默认的JDK序列化体积大、速度慢存到Redis里的字节数也大网络传输和内存占用都会上升。换成JSON序列化或者更高效的二进制序列化比如Protobuf、Kryo单条数据的体积能缩小一半以上。体积小了单次命令的传输时间就短了超时的概率自然降低。这个优化在数据量大、QPS高的场景下效果非常明显属于性价比极高的改造项。5. 实战案例一次线上Redis超时的完整排查复盘前面讲的是方法接下来还原一次我实际经历的完整排查过程。这个案例挺有代表性现象迷惑性强根因也不是最常规的那几个希望对你有参考价值。5.1 现象描述业务告警与日志特征某天下午业务方反馈线上接口偶发报错错误信息是Redis命令超时但重试一次就成功。客户端用的Lettuce报错内容是RedisCommandTimeoutException: Command timed out after 2 second(s)。刚开始频率很低大约每分钟几次到下午四点多突然变成每分钟上百次告警群里开始刷屏。我第一反应看了Redis服务端的CPU、内存、连接数全部正常INFO里connected_clients稳定在200左右远没到上限instantaneous_ops_per_sec也才几千。慢日志里只有几条耗时几十毫秒的命令完全不构成威胁。这组数据说明问题不在服务端常规指标上。5.2 排查过程从网络到服务端一步步收敛既然服务端表面看没问题我拉长观察维度。在出问题的业务服务器上执行redis-cli --latency延迟平均值正常但每隔几十秒会跳一次几百毫秒的尖峰。这个尖峰和业务报错的时间点对不上业务超时是持续的。接着我看Redis日志发现了一个关键线索日志里有周期性出现的Asynchronous AOF fsync is taking too long警告。这个警告的意思是AOF后台刷盘速度跟不上写入速度Redis为了安全会阻塞主线程等待刷盘完成。虽然我的配置是appendfsync everysec但在极端情况下如果磁盘IO跟不上刷盘耗时会被拉长造成主线程短暂的停顿。查看AOF重写的日志果然每次超时集中爆发的时间点都跟aof_rewrite执行时间吻合。再深入看这台Redis所在的云主机挂载的是普通云盘IOPS上限不高而AOF重写会生成大量随机写IO直接把云盘的IOPS打满导致正常的AOF刷盘也被拖慢。两条因素叠加Redis事件循环周期性卡顿客户端命令自然超时。5.3 根因确认与修复根因链条是这样的云盘IOPS上限太低AOF重写期间产生大量IO导致AOF同步刷盘阻塞Redis主线程周期性停顿客户端读超时。这是个典型的“云上基础设施瓶颈引发应用层超时”案例。修复分三步。第一步是临时缓解把AOF重写触发阈值调大减少重写频率redis-cli CONFIG SET auto-aof-rewrite-percentage 200 redis-cli CONFIG SET auto-aof-rewrite-min-size 4gb第二步是给Redis实例挂载更高IOPS的云盘或者把AOF文件放到单独的云盘上和RDB快照的写入路径隔离。第三步是调整AOF刷盘策略在可容忍丢失少量数据的场景下保持everysec不变但配合系统的磁盘调度参数优化。改动上线后AOF警告消失Redis超时告警归零--latency的尖峰也不再出现。后续复盘的时候我把latest_fork_usec和AOF阻塞时间做成了监控指标再遇到类似问题可以秒级定位。6. 排查工具箱命令、参数与配置速查表最后把常用命令和关键配置整理成速查表方便你排查时直接照着敲。6.1 客户端侧排查命令速查ping RedisIP测试基础连通性注意云环境可能禁pingtelnet RedisIP 6379测试端口连通性nc -vz RedisIP 6379端口连通性的脚本化替代方案redis-cli -h RedisIP -p 6379 --latency实测真实的Redis延迟redis-cli -h RedisIP -p 6379 --latency-history观察延迟的时间分布tcpdump -i eth0 host RedisIP and port 6379抓包分析TCP重传和丢包ss -s查看本机TCP连接状态汇总重点看TIME_WAIT数量6.2 服务端侧排查命令速查redis-cli INFO整体概览包含内存、连接数、CPU相关信息redis-cli INFO clients查看连接数相关指标redis-cli INFO stats查看运行统计重点看latest_fork_usecredis-cli INFO memory查看内存碎片率、峰值内存redis-cli INFO commandstats看每个命令的调用次数和耗时redis-cli SLOWLOG GET 50拉取最近50条慢日志redis-cli --bigkeys扫描大Keyredis-cli CONFIG GET maxclients查看最大连接数配置redis-cli CONFIG GET slowlog-log-slower-than查看慢日志阈值6.3 高频配置项参考配置项建议值说明timeout300客户端空闲N秒后关闭连接防止连接堆积tcp-backlog1024配合内核somaxconn应对高并发连接maxclients按需设置云上建议结合连接池总量预留余量slowlog-log-slower-than1000生产环境建议从默认10000调低到1000slowlog-max-len128慢日志保留条数排查时够用即可appendfsynceverysec大部分业务场景的均衡选择auto-aof-rewrite-percentage100-200控制AOF重写频率IO瓶颈时调大我个人在实际操作里的习惯是每个Redis实例上线时就把慢日志阈值调到1毫秒把latest_fork_usec、AOF阻塞耗时、连接数这三类指标接入监控超时告警触发时直接对着指标看十分钟内基本能确定根因方向。这个习惯帮我省了很多次深夜紧急排查。另外排查超时之前先把监控图打开没有监控就靠日志里的时间戳对照别凭感觉乱猜——大部分超时问题的答案其实都藏在“时间点对不上”的细节里。