Redis的CLIENT LIST命令我用了快十年,但每次排障都还能挖出点新东西。刚开始做某电商项目的缓存运维时,应用一压测Redis就卡死,大家第一反应都是“加机器”“调maxmemory”,结果两条CLIENT LIST输出甩出来,问题一目了然:一堆不健康的客户端连接把内存缓冲区撑爆了。也是从那以后,这个命令成了我排查Redis问题时必看的第一项。
CLIENT LIST能干什么?一句话:把当前Redis实例上所有客户端连接的快照列出来,包括每个连接的地址、名字、状态、空闲时间、缓冲区使用、正在执行的命令等。它不是给监控系统看指标的,而是给运维和开发“透视”连接真实状态的。适合谁用?只要你的Redis实例出现过连接数突增、延迟抖动、OOM内存告警、命令阻塞,或者你想搞明白客户端连接池到底有没有复用、有没有泄漏,这篇文章就能帮你省掉大把抓瞎时间。
1. 为什么需要CLIENT LIST:先讲几个真实场景
1.1 什么情况下你想起来用它
我印象最深的几次,都是“常规监控看不出来”的场景。
比如某次大促前夜,某应用服务刚上线,Redis的CPU和内存都很正常,但P99延迟涨了好几倍。用info命令看,连接数也不高,内存也没到上限。后来我通过CLIENT LIST发现,有个客户端连接正在执行一个KEYS *命令,bigkey瞬间阻塞了实例。CLIENT LIST里直接写着cmd=keys,那一刻你就知道是谁的锅。
再比如连接数耗尽的问题。Redis默认maxclients是10000,到了临界值后新连接报“max number of clients reached”。这时候你光看连接总数没用,还得知道哪些IP占得最多、哪个应用没关连接、哪个连接池没设置合理size。CLIENT LIST把每个连接的地址都列出来了,按IP聚合一下就能定位源头。
还有一类场景是分析空闲连接。很多客户端库如果没配keepalive或空闲超时,连接会一直挂着,看似无害,但它们在连接数计算和内存占用上都会消耗资源。CLIENT LIST里的idle字段能告诉你每条连接空置了多久,方便你设计合理的超时策略。
1.2 对应到实际运维的工作流
我一般排查Redis相关故障,会按这个顺序做:
先看info clients,拿整体连接数和阻塞连接数。然后CLIENT LIST | grep flag,重点看S(slave)和M(master)之外的用户连接。再按addr和cmd字段做简单聚合,找出异常源头。最后配合CLIENT KILL或CLIENT PAUSE做临时止血。
这套流程的核心就是CLIENT LIST。因为info只告诉你“有多少”,而CLIENT LIST告诉你“都是谁、在干嘛”。生产环境下的Redis排障,缺了这层信息基本就是盲猜。
2. 命令输出字段逐项拆解:把每列都看明白
CLIENT LIST的输出是一行一个客户端,字段用空格分隔,常见的有几十个。很多人只看前几个字段就完事了,其实后面那些才是定位问题的关键。
2.1 基础字段:id、addr、laddr、name、age、idle、flags、db
先说id和addr。id是连接的唯一编号,单调递增,NewRelic那类APM工具里经常会引用它。addr是客户端IP和端口,比如127.0.0.1:53412。注意这个端口是客户端的临时端口,不是Redis端口。laddr是当前连接在本机上的地址和端口,也就是客户端连到了Redis的哪个IP端口上。在多网卡或端口复用场景下,laddr还挺重要。
name是客户端连接名,默认空。CLIENT SETNAME命令可以给连接起个名字,像“order-service-pool-01”。如果你用了客户端连接池且没有显式setname,排查时就只能靠IP猜,弱爆了。age是连接已存在的秒数,idle是空闲秒数。这两个放一起,就能看出连接池是否长连接复用得比较好:如果age很大但idle很小,说明连接很活跃;如果idle接近age,说明这条连接建了但一直没用,明显是池子配置问题。
flags字段类型比较多,常见的有:
- N:普通客户端连接
- S:从库连接
- M:主库连接
- O:被monitor命令监视的连接
- x:处于Multi/Exec事务中
- b:被阻塞的命令,比如BLPOP、WAIT
- d:脏连接,等待被关闭
- u:连接未认证
- A:连接关闭中
- P:连接在Pub/Sub模式
我每次都会优先看有没有b和d。有b说明存在阻塞命令,可能是业务侧执行了BLPOP且没有数据,或WAIT等主从同步;有d说明系统检测到连接异常,可能TCP层面已经断了但应用还没感知。
2.2 缓冲区与内存字段:qbuf、qbuf-free、obl、oll、omem
这几个字段是最容易被忽略,但往往是最致命的。Redis为每个客户端维护了输入缓冲区和输出缓冲区,一旦业务没做好限流或者命令返回结果过大,缓冲区就会疯长。
qbuf是输入缓冲区已用字节数,qbuf-free是剩余字节数。默认输入缓冲区上限是1GB,超过后Redis会直接关闭连接。正常情况下qbuf很小,几十字节到几百字节。如果你看到qbuf很大,比如几百KB甚至几MB,说明有客户端在源源不断发命令,但Redis处理不过来,或者客户端在发一个超大命令,比如批量插入一条几十MB的数据。
obl是输出缓冲区中固定缓冲区已用字节数,oll是输出缓冲区中链表缓冲区已用字节数,omem是这条连接输出缓冲区占用的总内存字节数。omem是重点,它包含固定缓冲和链表缓冲以及协议转换临时内存的总和。正常情况下omem一般只有几千字节。如果某条连接的omem达到几MB甚至几十MB,那就是典型的“慢消费”问题:Redis处理命令很快,把结果一股脑写到了输出缓冲区,但客户端读取太慢,导致数据堆在服务端内存里。
这类问题通常出现在做KEYS *、SMEMBERS大集合、LRANGE大列表、或发布订阅大量消息时。如果这种情况持续,Redis的used_memory会被撑高,极端情况下直接OOM。我在实际排障中看到过一条连接的omem达到200多MB,那基本就是有人在用客户端工具直接执行了返回几十万条数据的命令,而工具界面还在慢慢渲染。
2.3 命令与转发字段:cmd、argv、redir、user
cmd字段表示这个连接当前正在执行的命令,如果空闲则为NULL或上次执行命令的名称。注意它不代表历史上执行过什么,只代表“此刻”在跑什么。如果你想排查慢查询,CLIENT LIST里看到的cmd不一定准,因为命令可能执行完了;但如果是阻塞类命令,它就会一直显示在那里。
argv是当前命令的最近几个参数,长度有限制,Redis版本不同显示长度也不同。如果是GET foo,会显示类似["GET","foo"]。这个字段很敏感,如果命令参数带有敏感数据,在CLIENT LIST里也会显示,需要注意安全审计。不过正常情况下你不容易看到太长参数,因为Redis只保留了有限长度。
redir字段只在客户端处于Monitor模式时才有意义,表示重定向目标客户端ID。一般我们不用太关注,但如果你看到常规连接带了这个字段,说明它被monitor订阅了,需要检查是谁在开着monitor,因为monitor对性能影响很大。
user字段是Redis 6.0 ACL功能开启后才有的,显示连接认证的用户名。默认是default。如果你用了多用户权限隔离,可以通过它快速定位谁在用哪个账号接入,排查权限问题时很好用。
3. 常用变体与参数组合:不只是裸跑一条命令
3.1 CLIENT LIST的ID和TYPE过滤
很多人只会直接敲CLIENT LIST,然后从一大坨输出里grep。其实Redis从4.0开始支持CLIENT LIST ID id1 id2这种写法,可以只返回指定ID的连接,配合刚才提到的redir字段和monitor场景很好用。比如你在日志里看到某个慢命令对应的客户端ID,就可以这样精确查。
从Redis 5.0开始增加了CLIENT LIST TYPE normal|master|replica|pubsub,可以按连接类型过滤。我最常用的就是TYPE normal,把主从复制的连接过滤掉,只看业务连接,输出瞬间干净很多。注意老版本里replica写作slave,如果脚本要兼容老版本,得留意这个差异。
3.2 与CLIENT INFO、CLIENT GETNAME/SETNAME配套使用
CLIENT INFO是Redis 6.2引入的,只返回当前连接自身的信息,格式和CLIENT LIST的一行一样。适合在客户端脚本里诊断自己这个连接的状态。比如你写了一个脚本想在Redis里跑点东西,又不想查整个列表,可以用CLIENT INFO判断当前连接的db、flags、name等。
CLIENT SETNAME和CLIENT GETNAME跟CLIENT LIST配合是绝配。强烈建议所有应用在初始化连接池之后,执行一下CLIENT SETNAME,把连接名设置为“服务名-实例名-用途”。这样后续看CLIENT LIST,就能直接认出哪类连接对应哪个服务,不用再拿IP找开发确认。我在某公司做过一个规范,强制要求所有Redis客户端在连接时设置name,排障效率提高了一个级别。
3.3 实战组合:CLIENT KILL、CLIENT UNBLOCK、CLIENT PAUSE
CLIENT KILL可以对异常连接做精确清理。老语法是CLIENT KILL addr:port,新语法支持CLIENT KILL ID id,或者带过滤条件:CLIENT KILL TYPE normal、CLIENT KILL ADDR ip:port、CLIENT KILL LADDR ip:port、CLIENT KILL USER username、CLIENT KILL MAXAGE age。很多人在清理时直接用IP kill,但同一个IP可能有多个连接,精准杀的时候用ID更安全。
CLIENT UNBLOCK是给阻塞中连接用的。如果某个客户端执行了BLPOP且一直没等到数据,你可以执行CLIENT UNBLOCK TIMEOUT|ERROR,让那条连接恢复。注意这个命令并不会中断事务,只是解除阻塞。生产上比如发布时想快速让阻塞命令退出,就可以用这个。
CLIENT PAUSE则是对所有连接做暂停,常用于主从切换或维护窗口。CLIENT PAUSE 10000 WRITE表示10秒内暂停所有写命令,但读命令不受影响。注意它不影响主从复制的内部连接。这个命令配合CLIENT LIST能帮你做“优雅摘流”。
4. 真实排障案例与脚本化解析:一条命令定位问题
4.1 用awk快速聚合IP和连接数
裸看CLIENT LIST输出是很难受的,几百行的不好扫。我一般现场就直接套awk:
redis-cli -a yourpassword client list | awk '{print $2}' | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head -20$2是addr=127.0.0.1:12345这样的字段,再按冒号切出IP,统计每个IP的连接数。如果某个IP连接数几千,基本可以确定是那个应用或那台机器连接泄漏或连接池设置过大。这时候不需要马上kill,先用CLIENT LIST的名字字段确认一下连接名。
另外统计空闲连接也很简单:
redis-cli client list | awk -F'idle=' '{split($2,a," "); if(a[1]>300) print $0}' | head -30把空闲超过300秒的连接列出来,看是不是有客户端没做空闲回收。我曾经在某个Java应用里发现连接池的minIdle设得很大,结果压测结束后几百条连接一直挂着,idle字段全部超过上千秒。调小minIdle之后,内存占用直接降下来。
4.2 利用flags和omem定位阻塞与缓冲区问题
排查P99延迟高的那次,我是这么做的:
先执行CLIENT LIST | grep -E 'flags=.*b|flags=.*d',看看有没有阻塞或脏连接。果然发现一条目标连接flags=b,cmd=keys,addr对应某运维工具的IP。直接联系那边确认,原来在用客户端工具浏览key,不小心点了全局匹配。CLIENT KILL ID 之后延迟立刻恢复。
再看omem异常的场景。某次某业务的Redis内存使用率飙到80%,查内存却找不到大key。后来执行CLIENT LIST,我按omem从大到小排序:
redis-cli client list | awk -F'omem=' '{print $2, $0}' | sort -rn | head -20发现一条连接omem=85673421,上下文cmd=smembers,果然后台任务在拉取一个大集合的全量成员,客户端消费又慢,导致Redis端堆积了85MB输出缓冲。这个案例再次验证:used_memory过高不一定是key大,也可能是客户端输出缓冲区大。
4.3 与monitor和latency配合的一整套排查
CLIENT LIST并不是孤立使用的。我的习惯是:
- 如果看cmd有大量get/set的简单命令,但延迟高,就开latency monitor或者用redis-cli --latency看实例级延迟。
- 如果看cmd有慢命令,比如keys、hgetall、smembers、lrange这类,直接定位到对应连接。
- 如果连接数很多但没人执行命令,那多半是连接池问题,用CLIENT LIST的idle和name辅助判断。
- 如果连接全部正常,再去看网络层面,比如网卡丢包、TCP重传。
有一次某业务反馈Redis偶尔超时,CLIENT LIST里没看到阻塞连接,omem也正常。后来用tcpdump看发现是客户端所在宿主机网络抖动,跟Redis无关。不装CLIENT LIST这步,你都不敢这么笃定地和网络团队说“问题不在Redis”。
5. 使用CLIENT LIST的注意事项与避坑心得
5.1 版本差异与字段变化
CLIENT LIST的输出结构在不同Redis版本里是有变化的。比如Redis 2.8时代只有二十几个字段,到Redis 6.2增加了laddr、redir、user、tot-net-in、tot-net-out等。你的脚本如果直接用固定字段位置,版本升级就可能错乱。
我的建议是不要按位置解析字段,而是按“字段名=值”的方式解析。因为顺序不保证稳定,但有=的结构一直稳定。用awk split成kv再取值,这样跨版本更安全。
另外需要注意的是,Redis 5.0之前TYPE replica写的是TYPE slave,如果要在老实例上用脚本过滤,需要注意这个词。我吃过这个亏,线上脚本在4.x实例上执行TYPE replica没报错但返回了全部连接,因为老版本不认识replica就直接忽略了过滤条件。
5.2 对性能的影响与频率控制
CLIENT LIST命令本身是O(N)的,N是连接数。如果连接数上万,执行一次CLIENT LIST的耗时会在几毫秒到几十毫秒。虽然比KEYS快多了,但频率过高会给Redis增加额外CPU开销,也会影响主线程。
我建议不要把CLIENT LIST放在每秒执行的监控脚本里。合理的做法是:常规监控用info clients的connected_clients和blocked_clients就行;当出现告警时,再手动或低频任务去拉CLIENT LIST分析。如果确实要实时监控客户端连接状态,可以用Redis 6.2里的CLIENT NO-EVICT和相应INFO字段,或者借助外部指标,而不是频繁调CLIENT LIST。
5.3 高版本命令的安全与ACL权限
Redis 6.0之后,CLIENT类命令受ACL控制。如果你给应用账号只授权了读写权限,没授权client管理权限,CLIENT LIST会报NOPERM。所以有些自动化脚本升级到Redis 6后突然失效,先检查ACL。
另外CLIENT LIST输出中包含命令参数(argv字段),如果存储了敏感信息,需要小心日志泄露。我记得某金融项目里审计要求禁止在日志中记录CLIENT LIST原始输出,就是因为argv可能带有密码或者身份证号。处理方式是用脚本摘掉argv字段再落日志。
在生产环境执行CLIENT KILL、CLIENT PAUSE这类操作前,一定先确认作用范围。特别是CLIENT PAUSE会影响所有客户端,最好在低峰期或者带上WRITE参数只暂停写命令。CLIENT KILL用ID比用IP安全,因为IP粒度太粗可能误杀同一台机器的其他应用连接。
5.4 从实战中总结的几条检查清单
最后分享一个我每次处理Redis连接类问题都会对照的清单:
- 先看连接总数是否接近maxclients。
- CLIENT LIST里flags=d的脏连接多不多,多就检查TCP keepalive。
- flags=b的阻塞连接对应什么命令,确认是否符合预期。
- omem超过10MB的连接,结合cmd确认是不是有批量拉取大key。
- qbuf特别大的连接,关注是否客户端在批量发送命令。
- idle很大的连接,核对连接池空闲回收策略。
- name是否规范,不规范的让对应应用补上连接名。
- 主从复制的连接也用TYPE replica过滤后看看,如果是主实例,确认slave连接是否都正常。
这套清单帮我处理过至少几十个线上案例。CLIENT LIST最厉害的地方在于,它把“连接”这个黑盒透明化了。你不需要猜客户端是怎么连的,直接看实例输出就行。如果你还没把CLIENT LIST纳入自己的Redis排障工具箱,下一次出问题的时候,第一件事记得跑一下它。