1. 从一条命令说起:为什么我建议运维先掌握CLIENT LIST
先抛一个很常见的场景:某天下午,线上Redis实例突然响应变慢,INFO里used_memory看着还行,hit ratio也没崩,但业务方反馈接口超时。你登录服务器敲了个redis-cli info clients,看到connected_clients从平时的三百多涨到了一千二,可这又能说明什么呢?连接多了,到底是业务流量涨了,还是有连接泄漏?哪些连接是空闲的,哪些在刷屏,哪些是主从复制用的,完全是一笔糊涂账。
这时候CLIENT LIST才是真正值得先敲的命令。它不是那种花哨的高级特性,但如果你能把它的输出看明白,绝大多数连接层面的问题都能在五分钟内定位到方向。
简单说,CLIENT LIST用来查看当前Redis实例上所有客户端连接的详细信息,每个连接一行,字段很多:从哪个IP来的、用了哪个端口、当前在干什么、空闲了多久、有没有事务、是否被阻塞、内存占了多少。它解决的问题很直接:当Redis出现异常时,快速判断是不是客户端连接导致的,并精准揪出“罪魁祸首”连接。
适合谁看?日常维护Redis的运维工程师、被线上问题折腾的Java/Go开发、以及刚接触Redis想加深理解的初学者。这篇不是官方文档的翻译,而是把我在几个项目里用这条命令排查真实故障的完整思路和细节写出来,包括那些没人提醒你、但实际一定会踩的坑。
2. 核心细节拆解:CLIENT LIST输出的每个字段都得看懂
2.1 字段全景:一行连接信息里到底藏了多少线索
执行redis-cli client list后,输出大概长这样:
id=3 addr=127.0.0.1:56324 laddr=127.0.0.1:6379 fd=8 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=4096 argv-mem=10 multi-mem=0 obl=0 oll=0 omem=0 tot-mem=22518 events=r cmd=client|list user=default redir=-1 resp=2 id=5 addr=127.0.0.1:56325 laddr=127.0.0.1:6379 fd=9 name= age=148 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 obl=0 oll=0 omem=0 tot-mem=22458 events=r cmd=get user=default redir=-1 resp=2首次接触的人很容易被这一长串吓到,我最早也差不多,后来慢慢摸清规律:把它拆成四组去看,本质上就通了。
第一组是身份信息:id(连接唯一标识)、addr(客户端IP和端口)、laddr(Redis本地地址和端口)、fd(文件描述符)、name(通过CLIENT SETNAME设置的连接名称)、user(ACL用户)。
第二组是状态信息:age(连接已存在秒数)、idle(空闲秒数)、flags(连接状态标志)、db(当前选中的数据库编号)、multi(事务内命令计数,-1表示不在事务中)。
第三组是内存/缓冲信息:qbuf(输入缓冲已用字节)、qbuf-free(输入缓冲空闲字节)、argv-mem、multi-mem、obl、oll、omem、tot-mem。
第四组是当前行为信息:events(正在等待的事件)、cmd(最近一次执行的命令)、redir(重定向相关,主要用于集群)、resp(RESP协议版本)。
2.2 flags状态位:读懂连接正处于什么生命周期
flags是最容易被忽略但信息量最大的字段,它不是单值,而是字母组合。我实际排查中见得最多的几种:
N:普通客户端,无特殊状态,这是绝大多数连接的常态。M:主节点(primary),在复制场景下,主库连接从库时会带这个标志。S:从节点(replica),从库连上主库后,主库视角下这个连接显示为S。x:连接正被CLIENT PAUSE暂停,或者处于事务中的MULTI/EXEC期间。b:客户端被阻塞,通常是因为WAIT、BRPOP、BLPOP或SUBSCRIBE等阻塞式命令。O:连接即将被关闭,可能是客户端发了CLIENT KILL,或超过了超时设置。d:该连接的key刚被某个事件标记为脏(dirty),一般不用太关注。A:连接被关闭,通常是最大内存淘汰策略触发或管理员主动操作。
flags里的字母是区分大小写的,小写和大写代表完全不同的状态。我见过有同事把S当成“slow”查了半天,实际上是replica。掌握这些标志,你才能精准回答一个最常见的问题:这个连接是正常业务连接,还是复制连接,还是已经被污染的坏连接。
2.3 缓冲与内存字段:qbuf、omem和tot-mem的隐藏含义
很多人只看addr和cmd,忽略了缓冲字段,但真正定位大key、慢查询、内存暴涨的时候,最有用的是这几个:
qbuf是输入缓冲区已用字节数。正常情况下,客户端发一条GET命令,qbuf只有几十字节。如果业务是用管道批量写、或者发送超大key/value,qbuf会飙升。这里有个关键点:单个客户端输入缓冲区被硬限制为1GB,超过就会被服务端强制断开,日志里会出现 "Closing client that reached max input buffer length" 的提示。如果你在CLIENT LIST里看到某条连接的qbuf高达数百MB,那基本可以断定是业务侧有超大请求或者管道发送异常,需要立刻处理。
omem是输出缓冲区已用字节数,它和obl、oll一起看:obl表示固定缓冲区的已用字节,oll表示链表缓冲区的对象数量,omem则是总字节数。输出缓冲区没有1GB那么大的硬限制,但如果你用CONFIG GET client-output-buffer-limit查看配置,会发现普通客户端默认是normal 0 0 0(无限制),从库是slave 256mb 64mb 60(硬限制256MB,软限制64MB持续60秒),发布订阅是pubsub 32mb 8mb 60。
我踩过一次很深的坑:一个业务用Redis做发布订阅,消费者处理慢,消息堆积在输出缓冲区,omem冲到几十MB没触发限制,结果就是Redis主线程频繁fork做持久化时内存翻倍,整机OOM。后来查CLIENT LIST才发现罪魁祸首正是那几条SUBSCRIBE连接。从此我养成习惯,每周扫一次omem超过10MB的连接。
tot-mem是这条连接总共消耗的内存,包括了输入输出缓冲和内部结构开销,是最直观的“这条连接吃了多少内存”的指标。如果你排查内存碎片,只看INFO里的used_memory_rss是不够的,配合CLIENT LIST按tot-mem排序,能快速找出哪几个连接是内存大户。
3. 实操玩法:不只是看一眼,CLIENT LIST的高级用法
3.1 按条件精确过滤:只查你关心的那类连接
Redis 6.0以上版本给CLIENT LIST加了过滤参数,这个功能实在太顺手了。用法是CLIENT LIST [TYPE type] [ID client-id],TYPE可选值有三个:
normal:普通客户端,排除主从复制和订阅类连接master:主节点连接replica:从库连接pubsub:发布订阅连接
实际案例:某团队怀疑是主从复制导致主库延迟,跑redis-cli client list type replica,发现只有两条S连接,排除法立刻完成,问题焦点转向业务端慢查询。这个过滤逻辑的价值在于:你不用再从几百上千行输出里人肉grep了。
另外ID过滤是精确查找指定连接。线上用CLIENT LIST ID 12345查一条可疑连接时,输出只含那一条,排查效率提升一个量级。
3.2 和INFO、MONITOR组合:一条命令变成一套诊断工具
CLIENT LIST单独用是一维视角,配上其他命令就成了立体诊断网络。我的习惯顺序是:
第一步INFO clients,看总览:connected_clients、blocked_clients、tracking_clients等,建立宏观印象。如果blocked_clients很高,说明大量连接在阻塞等待,需要配合CLIENT LIST看是哪些命令导致。
第二步CLIENT LIST,全量抓取连接状态,重点看flags里的b(阻塞)、idle异常大的(空闲连接)、cmd异常的(最近执行的命令)。
第三步SLOWLOG GET 50,如果CLIENT LIST里发现多个连接都在执行同一条命令,比如KEYS *或HGETALL一个超大哈希,那慢日志大概率能对上号。
举个例子:有一次某游戏服务器出现间歇性卡顿,我先看到INFO里connected_clients正常,但CLIENT LIST里出现了二十多条连接的cmd=hgetall,而且多条omem数值不小,说明返回结果集很大。再看SLOWLOG,果然HGETALL一个包含数十万个字段的哈希耗时3秒多。那个哈希是某运营系统写入的全局配置,平时没人读,某天新功能上线后每个请求都去读它,直接把Redis主线程拖垮了。解决方法是把大哈希拆成多个小哈希,或者改为本地缓存。
3.3 用CLIENT LIST定位连接数暴涨的根源
连接数从几百涨到几千,不等于就是业务并发上来了。用CLIENT LIST按addr看来源IP分布,能快速分辨是单机集中还是分布均匀。我遇到过addr里90%都来自同一台应用服务器的场景,最后查出来是那台机器上某个线程池配置错误,每次请求都新建连接且不释放,导致连接泄漏。配合netstat -anp | grep 6379 | wc -l可以互相印证,但netstat只看TCP层,看不出Redis应用层状态,CLIENT LIST的不可替代性就在这里。
如果你的Redis实例开启了timeout配置(空闲连接超时),CLIENT LIST里idle字段是判断“哪些连接永远不释放”的最直接依据。我一般这样统计:
redis-cli client list | awk '{print $5}' | cut -d= -f2 | sort -n | tail -20这条命令按idle秒数排序,只取最大的20个连接。如果发现一堆idle只有几秒的连接反复出现,说明应用连接池配置有问题;如果idle动辄几千秒还没断开,说明应用忘记关连接,同时Redis的timeout没设置生效。
4. 实战复盘:一次CLIENT LIST救场的完整过程
4.1 问题现象与初步判断
某电商项目做促销压测,Redis从库节点CPU突然飙到90%,主库正常。从库CPU高通常有三种可能:过期key主动删除、持久化RDB重写、或者大量读请求打到从库。运维同事第一时间去看从库的INFO,connected_clients稳定在500左右,没大波动,这有点反常——如果大量读请求打过来,CPU高但也应该伴随命令处理量上升,可total_commands_processed只涨了一点。
4.2 CLIENT LIST抓出真凶
这时候我建议他跑redis-cli client list,从结果里发现了一个反常细节:大量连接的cmd=replconf且flags=S。正常从库只有一条S连接,哪来几百条?再仔细一看addr,全部来自一个IP——正是另一个机房的一台老旧缓存中间件服务器。
原来这套Redis做了读写分离,有一个历史遗留的Java SDK版本在启动时会重复建立从库连接,且重连逻辑有bug,断线后不会清理旧连接,导致从库节点上堆积了几百条伪复制连接。这些连接会周期性发送replconf心跳,虽然单个命令很轻,但几百条叠加加上从库本身负载,CPU就上去了。
CLIENT LIST让我们一眼看出问题不是正常业务流量,因为addr和flags的组合太特殊了。后来通过CLIENT KILL TYPE replica先把连接清掉,再升级SDK,从库CPU立刻恢复正常。事后复盘,如果只依赖INFO的计数器,这个问题大概率还要排查更久。
4.3 CLIENT KILL的正确姿势与注意事项
CLIENT LIST负责发现,CLIENT KILL负责处理。常见用法:
# 杀掉某个IP的所有连接 redis-cli CLIENT KILL ADDR 192.168.1.100:12345 # 杀掉某个IP的所有连接(旧写法,只按IP,不含端口) redis-cli CLIENT KILL IP 192.168.1.100 # 按类型杀 redis-cli CLIENT KILL TYPE pubsub # 按ID杀 redis-cli CLIENT KILL ID 12345这里有几个细节必须提醒:
CLIENT KILL会直接断开客户端连接,业务侧如果没有重连机制,会立刻报错。执行前先想清楚。- 新版Redis还支持
CLIENT KILL USER、CLIENT KILL MAXAGE等更精细的过滤,但我用下来最顺手的是TYPE和ADDR。 - 如果只想断开空闲连接,可以用
CLIENT KILL ID <id>配合上面那个awk排序结果来批量处理,但建议写成脚本时加个dry-run先打印,不要直接执行。
5. 踩坑记录与问题排查速查表
5.1 容易忽略的细节坑
第一个坑:CLIENT LIST输出里有些字段只在特定版本存在。比如tot-mem是Redis 4.0引入的,argv-mem和multi-mem更晚,Redis 2.8时代的老版本没有这些字段。如果你线上是混合版本,写脚本解析时按字段名切割就会踩坑。稳妥做法是先redis-cli INFO server确认版本,再决定解析哪几个字段。
第二个坑:发布订阅连接的cmd字段显示的是订阅命令,而不是实际推送的消息。比如一条cmd=subscribe的连接,它不代表正在执行慢操作,只是处于订阅状态。判断订阅连接是否异常,要看的是omem和lastinteraction,而不是cmd。
第三个坑:CLIENT LIST输出的addr在IPv6环境下的格式处理。IPv6地址包含冒号,用冒号分割字段解析时会把地址截断。我见过有人写脚本用冒号做分隔符,在IPv6机器上直接乱套。正确做法是用split(" ")按空格切,再按等号取k/v,不要用冒号。
第四个坑:CLIENT LIST本来就是个O(N)命令,N是客户端连接数。连接数几千上万时,这个命令本身会占用一些主线程时间。虽然单次执行通常是毫秒级,但如果你写个脚本每秒钟跑一次,在高连接数场景下就是给Redis添乱。建议监控频率设在30秒以上。
第五个坑:开启了ACL的环境,CLIENT LIST可能不会显示所有连接的完整信息。默认用户可以看到所有连接,但受限ACL用户,即使有CLIENT LIST权限,能看到的信息也会被过滤。如果你发现CLIENT LIST输出内容变少了,先检查ACL权限,别急着怀疑Redis有bug。
5.2 常见问题速查表
| 现象 | 排查命令组合 | 可能的结论 |
|---|---|---|
| CPU高但命令量低 | CLIENT LIST | 大量伪复制连接或空闲连接心跳 |
| 内存增长异常 | CLIENT LIST看omem/tot-mem排序 | 发布订阅堆积或大key返回 |
| 间歇性卡顿 | SLOWLOG + CLIENT LIST | 慢命令集中在个别连接 |
| 连接数只增不减 | CLIENT LIST按idle排序 | 连接池泄漏或timeout未配置 |
| 集群访问异常 | CLIENT LIST看redir字段 | 客户端未正确处理MOVED重定向 |
| 写超时但读正常 | CLIENT LIST type master | 主从延迟导致写入积压 |
5.3 批量处理脚本辅助:awk和python一行的用法
排查时经常需要把CLIENT LIST输出转成结构化数据,我常用的两种方式:
# 按tot-mem内存占用排序,找出最占内存的20条连接 redis-cli client list | awk -F' ' '{for(i=1;i<=NF;i++){if($i ~ /^tot-mem=/){print $i, $0}}}' | sort -t= -k3 -nr | head -20 # 按idle空闲时间排序,找出最空闲的15条连接 redis-cli client list | awk -F' ' '{for(i=1;i<=NF;i++){if($i ~ /^idle=/){print $i, $0}}}' | sort -t= -k3 -nr | head -15如果你更习惯用Python,一段简单的解析逻辑可以把每行转成字典:
import subprocess def get_clients(host='127.0.0.1', port=6379): out = subprocess.check_output(['redis-cli', '-h', host, '-p', str(port), 'client', 'list']) clients = [] for line in out.decode().splitlines(): fields = dict(item.split('=', 1) for item in line.split(' ')) clients.append(fields) return clients clients = get_clients() big_mem = sorted(clients, key=lambda x: int(x.get('tot-mem', 0)), reverse=True)[:10] for c in big_mem: print(c['addr'], c['cmd'], c['tot-mem'])注意dict(item.split('=', 1))里的1很关键,因为addr字段里的IPv6地址可能本身就含等号(虽然少见),但用split('=', 1)能避免字段被切碎。
6. 构建系统化排查:给新人一条可落地的命令路径
刚接手Redis运维的同事总问我:CLIENT LIST这么多字段,到底该重点看哪几个?我的建议是根据问题症状选视野:
- 连接数暴涨:看
addr分布、idle、flags - 内存异常:看
tot-mem、omem、qbuf - 慢操作:看
cmd、age、idle - 主从异常:看
flags、redir
还有一个更系统的做法:把CLIENT LIST的监控纳入你的巡检脚本,定时抓取并自动告警。不需要太复杂,一条bash加crontab就够:
#!/bin/bash redis-cli client list | awk -F' ' '{for(i=1;i<=NF;i++){if($i~/^omem=/){split($i,a,"="); if(a[2]>10485760) print $0}}}'这条脚本可以找出输出缓冲超过10MB的连接,一旦触发说明有异常堆积。我把类似逻辑整合到巡检里后,有几个明显的收益:
第一,问题从被动等业务反馈变成主动发现。以前是业务方说Redis连接异常,我再去查;现在是我先看到某条连接omem飙升,提前通知业务侧调整。
第二,因为CLIENT LIST信息太全,排查时间从小时级缩短到十几分钟。很多问题只要扫一眼flags和cmd,就能锁定方向,剩下的只是验证。
第三,方便沉淀知识库。把每次CLIENT LIST抓到的异常案例截图或记录,时间久了就是一份完整的状态特征库,新同事遇到相似情况可以直接对号入座。
我个人在实际操作中的一个体会是,CLIENT LIST虽然只是“看一眼”命令,但它比很多花哨的监控工具都可靠。监控系统只能告诉你“出问题了”,CLIENT LIST告诉你“具体是哪个连接、哪个IP、在干什么、干了多久”。有了这个维度,Redis连接层的排查从“猜”变成了“看”。
最后再分享一个扩展思路:如果你的连接数长期处于高位,CLIENT LIST的输出会有几千甚至上万行,人眼看不过来。这时候可以把输出导入到日志分析平台,按addr、flags、cmd做聚合统计,自动发现“某个IP的500条连接全部在执行同一条命令”这类异常模式。这个思路放在Redis 7的CLIENT LIST输出上同样适用,字段更丰富。排查工具不用太多,把一条命令吃透,胜过装一堆复杂的监控组件。