Redis 连接排查利器:CLIENT LIST 命令字段与实战全解
如果你维护过 Redis 实例大概率经历过这样的场景凌晨监控告警炸了maxclients被顶满新连接根本进不来业务侧全是连接超时。这时候我脑子里第一个冒出来的命令就是CLIENT LIST。它能把当前实例上每一个客户端连接的身份信息、行为状态、内存占用全部摊开给你看是整个连接排障体系的起点。这篇文章不绕弯子就把CLIENT LIST的用法、字段含义、排查套路、配套命令完整讲透适合正在被连接问题折磨的后端开发、负责缓存运维的同学以及刚学 Redis 想弄明白连接到底是怎么回事的新手。1. 连接数告警时为什么第一件事是敲 CLIENT LIST1.1 一条命令拿到全量连接快照CLIENT LIST的用法极其简单在redis-cli里直接执行即可redis-cli client list输出没有表头一行就是一条连接每行由一堆字段值组成大概长这样id5 addr127.0.0.1:51234 laddr127.0.0.1:6379 fd7 name age0 idle0 flagsN db0 sub0 psub0 ssub0 multi-1 watch0 qbuf0 qbuf-free20448 argv-mem0 multi-mem0 tot-mem21056 rbs1024 rbp0 obl0 oll0 omem0 eventsr cmdclient|list userdefault redir-1 resp2 id8 addr192.168.10.5:39012 laddr127.0.0.1:6379 fd12 nameorder-service-3 age120 idle3 flagsS db0 sub0 psub0 ssub0 multi-1 watch0 qbuf0 qbuf-free20448 argv-mem0 multi-mem0 tot-mem21408 rbs1024 rbp0 obl0 oll0 omem0 eventsr cmdreplconf userdefault redir-1 resp2 id11 addr10.20.1.2:50210 laddr127.0.0.1:6379 fd19 name age3600 idle1800 flagsN db0 sub0 psub0 ssub0 multi-1 watch0 qbuf0 qbuf-free20448 argv-mem0 multi-mem0 tot-mem21088 rbs1024 rbp0 obl0 oll0 omem0 eventsr cmdget userdefault redir-1 resp2连接数怎么数因为输出没有表头直接wc -l就是连接数redis-cli client list | wc -l这个数字和INFO clients里的connected_clients理论上一致可能略多一两行因为执行CLIENT LIST的这个连接本身也会被列出来。需要注意线上如果接入了堡垒机或代理你看到的实际上可能是代理与 Redis 之间的连接而不是业务应用的原始 IP这种情况后面会专门讲。1.2 CLIENT LIST 和 INFO clients 的分工很多新手会问INFO clients里不是也能看到连接数吗为什么要用CLIENT LIST答案是粒度完全不同。INFO clients返回的是一组汇总指标# Clients connected_clients:32 blocked_clients:3 tracking_clients:0 clients_in_timeout_table:0它能告诉你有多少连接、有多少阻塞中的连接但给不出任何细节。而CLIENT LIST是逐连接的明细数据每个连接几十个字段能回答的问题包括是哪台机器连过来的、这个连接空闲多久了、它在跑什么命令、它占了多少内存、它的输出缓冲区是不是已经膨胀到要撑爆内存了。做个类比INFO clients是体检报告的封面只给总检结论CLIENT LIST是血液化验单的每一项指标。真要定位问题只靠封面是不够的必须翻到每一项。2. 输出字段逐个拆解看懂每一列在说什么很多人在CLIENT LIST面前发怵是因为一行太长字段太多。其实这些字段可以分成三组身份识别、活动状态、缓冲区与内存。分组记忆会轻松很多。2.1 身份识别类字段这个连接到底是谁先看一组描述连接本体的字段字段示例含义id11连接的唯一 ID全局递增排障时用于精准定位addr10.20.1.2:50210客户端地址格式是 IP:端口laddr127.0.0.1:6379服务端本地的监听地址多网卡、多 IP 部署时有参考价值fd19该连接对应的文件描述符编号可以和系统层的排查结合nameorder-service-3客户端名称由CLIENT SETNAME设置不设置则为空db0该连接当前选中的逻辑数据库编号userdefaultACL 认证用户名Redis 6.0 之后存在resp2客户端使用的 RESP 协议版本RESP2 还是 RESP3addr是初看最直观的字段但注意这里的 IP 是客户端到 Redis 的实际 TCP 对端地址。如果业务应用通过负载均衡、网关或者云数据库代理访问 Redis这里看到的是代理节点 IP真正的应用 IP 需要结合代理日志再查一层。name字段在排障时价值极高后面专门讲如果团队没有统一设置连接名的习惯这个字段通常空着你也就失去了最快定位业务归属的抓手。2.2 活动状态类字段这个连接正在做什么第二组字段反映连接的实时行为也是最需要结合业务语义理解的字段示例含义age3600连接已经存活的秒数idle1800距离上一次有命令交互的秒数flagsN / S / b / x连接状态标志可以组合出现sub0常规频道订阅数psub0模式订阅数ssub0shard 频道订阅数Redis 7.0 后才有multi-1事务中已排队的命令数-1 表示当前没有事务watch0该连接 WATCH 的键数量eventsr / w文件事件r 表示连接可读w 表示可写cmdget当前正在执行的命令flags是最需要展开的字段。常见的标志有这么几个N无特殊标志就是一个普通连接。S从节点副本连接到主节点的连接M相反是主节点连接到从节点的连接。看到S别急着杀那是复制链路的一部分。OMONITOR 模式的客户端。生产环境开着 MONITOR 的客户端会让 Redis 处理速度显著下降如果发现大量O标志连接要考虑是不是有人在进行诊断操作忘了关。b连接正处于阻塞状态比如正在执行BLPOP、BRPOP、XREAD BLOCK等命令。看到flagsb且cmdblpop是正常的说明有消费者在等待队列消息。x连接处于 MULTI/EXEC 事务上下文中已经发了 MULTI但还没有 EXEC。u通过 Unix socket 连接不是 TCP。A连接已经被标记为 ASAP 关闭通常是输出缓冲区超过限制Redis 准备强制断开这种连接往往还伴有巨大的omem。c连接会在这个请求回复写完后关闭。d当前这个正在执行CLIENT LIST的连接本身。排查时扫一眼flags就能大致给连接分类。比如某台 redis 实例连接数几千你发现其中关键业务的连接 flags 都是bcmd 都是blpop那说明消费者都在正常等消息如果有一批连接flagsN、idle0、cmd 都是get但业务却报超时那问题可能不在 Redis 而在应用侧或者连接根本没有被有效复用。2.3 缓冲区与内存字段连接吃掉的资源第三组字段是很多资深运维最爱看的因为它们和内存、稳定性直接相关字段示例含义qbuf0查询缓冲区当前已使用的字节数qbuf-free20448查询缓冲区剩余可用空间argv-mem0解析后的命令参数占用的内存multi-mem0事务中排队命令占用的内存tot-mem21056该连接整体消耗的内存rbs1024固定回复缓冲区大小rbp0固定回复缓冲区已使用量obl0输出缓冲区固定部分长度oll0输出缓冲区链表部分长度omem0输出缓冲区总内存占用简单理解客户端发命令过来字节先堆积在qbufRedis 解析后放进argv-mem执行完的返回结果先写进输出缓冲区用obl、oll、omem统计。tot-mem就是这条连接吃掉的综合内存。我把这三块打个比方qbuf是餐厅门口等待叫号的排队区客人还没进去argv-mem是已经拿到菜单开始点菜的客人输出缓冲区是后厨做好但外卖小哥一直没来取的餐越堆越多omem就是那堆没人取的餐量。正常情况下omem应该很小几 KB 到几十 KB如果某个连接的omem涨到几 MB、几十 MB说明它点了大量餐却不取走——典型情况是 Pub/Sub 消费者消费速度跟不上生产速度或者应用代码长时间不读取 socket 数据。3. 用 CLIENT LIST 定位线上问题的三段排查链命令会用了字段能看懂了接下来聊实战。下面是我实际处理过的高频问题完整的排查思路和操作命令都在这。3.1 连接数打满先按 IP 聚合再精准击杀场景业务侧批量报错ERR max number of clients reachedINFO clients显示connected_clients已经接近maxclients。第一步别急着杀连接先看连接是哪里来的。执行redis-cli client list | awk {$0substr($0, index($0,$2)); split($2,a,:); print a[1]} | sort | uniq -c | sort -rn稍微解释下这个命令的思路addr在连接行里基本是第二个字段老版本没有laddr时是第二个新版本有laddr时addr依然排第二取出来按冒号切分只留 IP 部分然后按 IP 统计连接数从大到小排序。如果嫌上面太长也可以用更稳妥的匹配写法redis-cli client list | awk { for(i1;iNF;i){ if($i ~ /^addr/){ split($i,a,:); cnt[a[2]] } } } END{for(v in cnt) print cnt[v], v} | sort -rn这里为了避免误踩字段顺序的坑主动去匹配以addr开头的字段只要 Redis 输出格式不大改这段脚本是健壮的。聚合结果一看某个 IP 的连接数占了一半。这个 IP 通常就是没写连接池的应用主机、或者配置了错误重试策略的服务。接下来做两件事确认业务归属联系负责团队检查连接池配置最大连接数、空闲回收时间等。临时止血用CLIENT KILL清理该 IP 的多余连接。redis-cli client kill addr 192.168.1.23:52345CLIENT KILL一次只能按一个精确的IP:端口杀如果想批量清某个 IP 的所有连接可以用脚本遍历redis-cli client list | grep addr192.168.1.23: | awk {for(i1;iNF;i) if($i ~ /^id/) print $i} | cut -d -f2 | while read id; do redis-cli client kill id $id done这里从CLIENT LIST里筛出目标 IP 的连接提取id字段逐个调用CLIENT KILL ID。为什么不直接用ADDR因为 IP 固定但端口不固定需要一条条处理。注意杀连接前务必确认目标不是你自己的管理通道、不是主从复制连接。复制连接在flags里能看到S或M这种连接如果被误杀主从复制会中断重建产生不必要的全量同步开销。稳妥做法是先CLIENT LIST TYPE normal只看普通连接再动手。3.2 阻塞和事务从 flagsb 看排队真相另一个常见场景是延迟突增、业务超时直觉上怀疑 Redis 被阻塞了。这时候INFO clients里的blocked_clients指标会跳出来但先别急着下结论。执行CLIENT LIST重点筛出 flags 含b的连接redis-cli client list | grep flagsb | awk {print $2, $9, $NF}flagsb表示该连接正在阻塞等待某个事件比如BLPOP、BRPOPLPUSH、XREAD BLOCK。这类连接多只说明有很多消费者在等着新数据进来。这个状态对队列、延迟任务、消息通知类场景是常态不代表 Redis 卡住了。真正需要警惕的是另一种阻塞flagsN但cmd显示的是GET、SET这类普通命令同时age很小、idle0说明命令发进去后 Redis 没有立刻返回。这种一般是 Redis 内部在跑慢命令比如KEYS、大SORT、XREVRANGE拉取超大范围的数据或者宿主机 CPU 被打满、内存 swap 严重。当时我排查某个报表服务的毛刺问题CLIENT LIST里发现一个cmdkeys的连接argv-mem只有几十字节但 Redis 整体延迟高了几十毫秒。顺着cmd找到应用调用方把KEYS换成了分页SCAN问题立刻消失。这个排查链路的关键点在于CLIENT LIST能告诉你每一条连接正在执行什么而慢命令监控只能告诉你有过哪些慢命令两者结合才是完整拼图。还有一类比较隐蔽的情况连接显示flagsx处于 MULTI 事务中multi字段大于等于 0说明事务里已经塞了多条命令但还没 EXEC。如果事务里混入了大命令整个事务的执行时间会被拉长期间该连接的事务锁会一直持有。定位方法同样是看CLIENT LIST里的flagsx和multi字段。3.3 输出缓冲区异常膨胀用 omem 揪出慢消费者实例内存莫名上涨INFO memory里used_memory一直爬但BIGKEYS扫描也没发现大 key。这种情况我会直接看CLIENT LIST里omem和tot-mem最大的几个连接。列出omem最大的 20 条连接redis-cli client list | awk { for(i1;iNF;i){ if($i ~ /^omem/) {split($i,a,); omema[2]} if($i ~ /^addr/) {addr$i} if($i ~ /^cmd/) {cmd$i} } print omem, addr, cmd } | sort -rn | head -20如果前排连接omem动辄几十 MB而且cmd是subscribe或psubscribe基本可以断定是 Pub/Sub 消费者消费太慢。Redis 会把发布的消息推给所有订阅者只要有一个订阅者不读数据它的输出缓冲区就会持续膨胀直到触发client-output-buffer-limit pubsub的限制被强制断开然后重新连接、再次膨胀形成内存抖动。处理思路分两步立即止血把异常消费者踢掉redis-cli client kill addr 10.20.1.5:33021长期治理则要考虑架构层面是否适合 Pub/Sub。Pub/Sub 是广播语义消费者掉队了不会重放消息慢消费者会拖累整个实例这时候改用 Stream 配合消费者组通常更合适Stream 能把消息暂存在服务端消费者恢复后可以继续消费。如果膨胀的是普通normal类型连接多半是应用代码没有及时读取响应比如用了同步客户端却在一个线程里发了大量请求不读结果或者大 key 查询返回体积巨大。这两种情况的共同点都能在omem上暴露出来。所以我把omem列为连接排查里性价比最高的排查字段之一因为它直指问题本质有数据在堆积你还没读。4. 过滤参数和组合命令把 CLIENT LIST 变成管理工具光看不够还得能处理。Redis 在CLIENT命令族里提供了不少配套能力组合起来才是完整的连接治理方案。4.1 TYPE 和 ID 过滤只看你想看的连接连接多的时候CLIENT LIST全量输出一次可能非常大靠grep过滤不够优雅。Redis 提供了内置过滤参数。从 Redis 5.0 开始支持按类型过滤# 只看普通客户端连接 redis-cli client list type normal # 只看主从复制中从节点建立的连接 redis-cli client list type replica # 只看 Pub/Sub 连接 redis-cli client list type pubsub从 Redis 6.2 开始支持按 ID 过滤redis-cli client list id 5 8 11我在实际中用得最多的是type normal。排查业务连接问题时从库连接、复制心跳这类连接往往会干扰视线过滤掉之后输出更干净。比如上面 3.1 节的按 IP 聚合脚本如果先加上type normal就不会把复制链路误统计进去脚本可以简化成redis-cli client list type normal | awk {split($2,a,:); print a[2]} | sort | uniq -c | sort -rn4.2 配合 CLIENT KILL 精准断开异常连接CLIENT KILL是处理问题连接的手术刀不同版本支持的过滤条件不同。常用的几种形态# 按连接 ID 断开最精准 redis-cli client kill id 1234 # 按客户端地址断开 redis-cli client kill addr 192.168.1.23:52345 # 按客户端名称断开 redis-cli client kill name order-service-3 # 按 ACL 用户断开 redis-cli client kill user legacy-user # 断开空闲时间超过 3600 秒的连接较新版本支持 redis-cli client kill maxage 3600执行后返回OK表示至少杀掉了匹配的一个连接如果没有匹配会返回一条错误信息。MAXAGE这个参数在处理连接泄漏时很实用一条命令就能清掉所有闲置超过一小时的空连接不用再写循环脚本。使用CLIENT KILL有几个细节要注意一是当心杀掉自己的连接如果你的 redis-cli 来源 IP 和被杀条件匹配会把自己断开二是不要在从库上按TYPE replica去杀连接这会破坏主从关系三是按name杀连接时如果有两个服务用了同一个连接名会一起被断开所以连接命名必须唯一。4.3 给连接起名字CLIENT SETNAME 让排查效率翻倍CLIENT SETNAME是容易被忽视但回报极高的命令。它给当前连接设置一个名字之后这个名字会出现在CLIENT LIST的name字段里也会出现在慢日志的客户端信息中。命令行手动设置redis-cli client setname my-ops-conn redis-cli client getname业务代码里也建议统一配置。以 Python 的 redis-py 为例r redis.Redis( host10.0.0.8, port6379, client_nameorder-service-prod-01, )Java 的 Lettuce、Jedis 也都有对应的 client name 配置。建议命名规范是项目名-环境-实例序号比如order-service-prod-01、pay-service-gray-02。有了连接名之后CLIENT LIST的输出里name字段一目了然按业务聚合连接就非常简单redis-cli client list type normal | grep nameorder-service- | wc -l我曾经接手过一套混乱的 Redis 环境所有业务方都没有设置连接名出问题时只能靠 IP 反查。后来推动团队把连接名纳入上线 checklist之后再看CLIENT LIST每条连接的归属瞬间清楚排障时间从小时级降到分钟级。这一步投入极小收益却是长期的。5. 版本差异与生产环境使用建议5.1 不同版本的输出字段与过滤参数差异CLIENT LIST不是一成不变的不同 Redis 版本输出的字段和可用参数差异明显。我整理了一份简表供参考Redis 版本主要变化2.8基础字段完整CLIENT KILL仅支持按 ADDR 断开4.0输出调整新增部分内存字段连接 ID 引入5.0支持CLIENT LIST TYPE过滤重写客户端内存管理新增qbuf-free、argv-mem、tot-mem字段6.0ACL 正式推出输出新增user字段新增laddr支持CLIENT TRACKING相关字段redir6.2支持CLIENT LIST IDCLIENT KILL支持更多过滤条件新增CLIENT INFO7.0新增ssubshard 订阅、resp字段这意味着如果你用脚本解析CLIENT LIST不能假设字段顺序永远不变。上面的 awk 脚本里主动匹配addr、omem而不是依赖固定列位置正是为了兼容不同版本。生产环境的 Redis 升级之后原先跑得好好的分析脚本可能因为多了一个字段而索引用错这个坑我在实际中踩过。5.2 CLIENT LIST 本身也有代价CLIENT LIST的复杂度是 O(N)N 是当前连接数。连接数几千时执行毫无压力但连接数上了五位数并且在高并发时段频繁执行它对主线程还是会造成可见的 CPU 开销因为 Redis 需要遍历所有客户端连接并格式化字符串。输出也可能一次拉到几 MB。所以我建议监控告警尽量用INFO clients里的汇总指标CLIENT LIST作为人工排障手段不要做成每秒拉一次的监控脚本。如果确实需要周期性采集连接明细频率控制在分钟级并且加上type normal过滤减少数据量尽量避开业务高峰的每秒级别采样。另外CLIENT LIST输出格式在不同版本可能有细微变化脚本解析时不要依赖固定列位置像前面示例那样用字段匹配的方式更稳妥。5.3 团队规范与止血预案结合多年踩坑经验我建议在团队层面把下面几条做成规范第一应用必须统一设置CLIENT SETNAME连接名包含项目和实例信息。这条看起来简单但执行到位能省掉大量跨团队沟通成本。第二合理配置maxclients和timeout。maxclients不要盲目调大过大容易把 Redis 宿主机的文件描述符和内存耗尽反而引发雪崩。timeout参数决定空闲连接多少秒后被服务端回收默认 0 表示不回收这个要根据业务特点设置比如普通的缓存连接设 300 秒左右比较安全但要小心长连接业务和主从复制连接不受该参数影响。第三定期巡检CLIENT LIST。我用一个简单的 shell 脚本每小时检查一次最大omem连接和连接数 TOP 10 的 IP写成告警项#!/bin/bash redis-cli client list type normal | awk { for(i1;iNF;i){ if($i ~ /^omem/) {split($i,a,); ma[2]} if($i ~ /^addr/) {addr$i} } if(m 10*1024*1024) print big-omem, m, addr }超过阈值就报警。把omem纳入日常巡检后很多内存抖动问题能在业务感知前就被发现。第四准备一套连接止血预案连接数打满时先按 IP 聚合、确认归属、CLIENT KILL临时清理omem异常时判断是 Pub/Sub 慢消费者还是大 key 返回分别采用踢连接和优化查询的方式处理阻塞类问题通过flags区分是预期阻塞还是慢命令导致的非预期阻塞后者顺着cmd定位到具体调用方。我个人在实际操作中的体会是CLIENT LIST用得越多越觉得它像一张连接世界的实时地图——每次线上连接出问题先不急着重启服务、扩容机器而是静下心看看这张地图上的每一个连接在哪、在干什么、占了多少资源往往问题答案就藏在那几行不起眼的字段里。最后分享一个小技巧排查时千万别忘了CLIENT LIST里那个cmd字段它记录的是连接此刻正在执行或最后执行过的命令有时候一个诡异的cmdkeys比任何监控图表都更早暴露真相。