Redis客户端管理和异常分析
前言
Github:https://github.com/HealerJean
1、客户端管理和异常分析
命令 | 优点 | info clients |
---|---|---|
client list |
能精准分析每个客户端来定位问题 | 执行速度较慢,尤其是在连接数较多的情况下,频繁执行可能阻塞Redis |
info clients |
执行速度比client list快,分析过程较为简单 | 不能精准定位到客户端,不能显示所有输入缓冲区的总量,只能显示最大量 |
1.1、client list
说明:这里我同一个redis开启了两个客户端
127.0.0.1:6379> client list
id=3808 addr=127.0.0.1:55369 fd=8 name= age=5050 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=3810 addr=127.0.0.1:56759 fd=7 name= age=27 idle=26 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
1.2、标识
解析 | 说明 |
---|---|
标识 | |
id | 客户端连接的唯一标识 |
addr | 客户端链接的ip和标识 |
name | 客户端的名字 |
age | 客户点已经链接的时间 ,连接redis的时间为27秒 |
idle | 最近一次的空闲时间 redis空闲了26秒(当Redis连接的时间等于空闲时间的时候,就说明连接一直处于空闲,这种情况就是不正常的) |
flags | 客户端 flag |
db | 该客户端正在使用的数据库 ID |
sub | 已订阅频道的数量 |
psub | 已订阅模式的数量 |
multi | 在事务中被执行的命令数量 |
qbuf | 输入缓冲区已使用总容量 |
qbuf-free | 输入缓冲区剩余容量 |
obl | 固定缓冲区的长度 举例,obl=0 ,固定缓冲区长度为0 |
oll | 动态缓冲区的长度,举例:oll=4869 ,动态缓冲区有4869个对象 |
omem | 总的使用的字节数,举例:omem=133081288 ,两个部分共使用了133081288字节=126M内存 |
events | 文件描述符事件 |
cmd | 最近一次执行的命令 |
1.3、输入缓冲区
redis为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时Redis会从缓冲区拉取命令并执行,输入缓存区为客户端发送命令到Redis提供了缓存功能。
**输入缓冲区会根据输入内容的大小而动态调整,只是要求缓冲区的大小不超过1G,超过后客户端将关闭**
解析 | 说明 |
---|---|
qbuf |
输入缓冲区已使用总容量 |
qbuf-free |
输入缓冲区剩余容量 |
1.3.1、那么造成输入缓冲区过大的原因
1、Redis的处理速度跟不上输入缓冲区的输入速度,并且每次进入缓冲区的命令包含了大量bigkey
2、Redis发生了阻塞,短期内不能处理命令,造成客户端输入的命令积压在缓冲区
1.3.2、如何快速发现和监控
1、通过定期执行·client list
命令,收集qbuf
和qbuf-free
找到异常的连接记录并分析,最终找到可能出问题的客户端
2、通过info
命令的info clients
模块,找到最大的输入缓冲区client_biggest_input_buf:0
,例如,可以设置10M就开始报警
127.0.0.1:6379> info clients
# Clients
connected_clients:2
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
127.0.0.1:6379>
1.4、输出缓冲区
redis为每个客户端分配了输出缓冲区,它的作用是保存命令执行的结果返回给客户端。
实际上输出缓冲区由两部分组成,固定缓冲区和动态缓冲区,其中固定缓冲区返回比较小的执行结果,而动态缓冲区返回比较大的结果,例如大的字符串,
hgetall
、smembers
命令的结果。固定缓冲区使用的是字节数组,动态缓冲区使用的是列表。当固定缓冲区存满后会将
Redis
新的返回结果存放到动态缓冲区的队列中。队列中每个对象就是每个返回结果
127.0.0.1:6379> client list
id=3808 addr=127.0.0.1:55369 fd=8 name= age=5050 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=3810 addr=127.0.0.1:56759 fd=7 name= age=27 idle=26 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
参数 | 说明 |
---|---|
obl |
固定缓冲区的长度 举例,obl=0 ,固定缓冲区长度为0 |
oll |
动态缓冲区的长度,举例:oll=4869 ,动态缓冲区有4869个对象 |
omem |
总的使用的字节数,举例:omem=133081288 ,两个部分共使用了133081288字节=126M内存 |
1.4.2、配置输出缓存区的容量
与输入缓冲区不同,它的容量是可以通过参数配置的,。并且按照客户端的不同分为三种,普通客户端,发布订阅客户端,slave客户端。
客户端缓冲区超过
<hard limit>
,客户端会立即关闭;如何客户端缓冲区的输出缓冲区超过了
<soft limit>
并且持续了<soft secontds>
秒,客户端会被立即关闭
默认
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 8mb 2mb 60
1.4.2、监控输出缓冲区的方法
1、定期执行client list
模块命令,收集obl
、oll
、omem
找到异常的连接记录并分析,最终找到可能出问题的客户端
2、通过info命令的info clients
模块,找到输出缓冲区列表的最大对象数client_longest_output_list
127.0.0.1:6379> info clients
# Clients
connected_clients:2
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
127.0.0.1:6379>
1.4.3、发现和预防输出缓冲区出现异常
1、进行上述监控,设置阀值及时处理
2、限制普通客户端输出缓冲区参数的<hard limit> <soft limit > ,<soft seconds>
,把错误扼杀在摇篮中,例如可以进行如下设置
client-output-buffer-limit normal 20mb 10mb 120
3、适当增大slave的输出缓冲区的<hard limit> <soft limit > ,<soft seconds>
,如果master节点写入较大,slave客户端的输出缓冲区可能也会比较大,一旦slave客户端链接因为输出缓冲区溢出而别杀死,会造成重复重连
2、最大客户端连接数和超时时间
2.1、查看maxclients
和timeout
属性值
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "10000" //默认10000
127.0.0.1:6379> config get timeout
1) "timeout" //默认是0
2) "0"
127.0.0.1:6379>
2.2、限制
2.2.1、限制最大连接数
Redis提供了
maxclients
参数来限制最大客户端连接数,一旦超过maxclients
,新的连接将会被拒绝。
maxclients
默认值是10000,可以通过info clients
来查看当前的连接数
127.0.0.1:6379> info clients
# Clients
connected_clients:2 //当前的连接数
client_longest_output_list:0 //客户端输出缓冲区列表最大对象数
client_biggest_input_buf:0 //客户端输入缓冲区最大值
blocked_clients:0
127.0.0.1:6379>
2.2.2、timeout
一般情况下来说
maxclients=10000
在大部分场景已经绝对够用,但是某些情况由于业务方使用不当。例如,没有主动关闭连接,可能存在大量的
idle
空闲连接,无论是从网络连接成本还是超过maxclients
的后果来说都不是什么好事,因此Redis提供了一个timeou
来限制最大空闲连接,一旦空闲连接超过了timeout
,连接将会被关闭可以将
timeout
设置为300秒,同时客户端加上空闲检测和验证等措施
参数名字 | 内容 | 默认值 |
---|---|---|
minEvictableIdleTimeMillis | 连接的最小空闲时间,达到这个值后空闲连接将被移除timeBetweenEvictionRunsMillis大于0时才有意义; | 默认30分钟 |
timeBetweenEvictionRunsMillis | 空闲检测周期(单位毫秒) | -1,表示永不检测 |
testWhileIdle | 向连接池借用连接时是否做连接空闲检测,空闲超时的连接将会被移除 | false |
3、客户端命名、杀死、暂停、监控
3.1、客户端命名命名
这样比较容易标识出客户端的来源
127.0.0.1:6379> client setName clent1
127.0.0.1:6379> client getName
"client1"
127.0.0.1:6379>
127.0.0.1:6379> client list
id=3818 addr=127.0.0.1:62540 fd=7 name= age=4 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
127.0.0.1:6379> client setName clent1
OK
127.0.0.1:6379> client list
id=3818 addr=127.0.0.1:62540 fd=7 name=clent1 age=15 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
127.0.0.1:6379>
3.2、杀死指定ip地址和端口的客户端
client kill 127.0.0.1:62569
127.0.0.1:6379> client list
id=3819 addr=127.0.0.1:62555 fd=7 name=client1 age=153 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=3820 addr=127.0.0.1:62569 fd=8 name= age=102 idle=102 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
127.0.0.1:6379> client kill 127.0.0.1:62569
OK
127.0.0.1:6379>
3.3、客户端暂停阻塞
使用范围:只对普通发布者和订阅发布者有效,对于主从复制是无效的,生产环境中成本非常高
127.0.0.1:6379> client pause 10000
……
3.4、monitor
用于监控Redis正在执行的命令
另一个客户端B输入命令,则客户端A,
monitor
可以监听到正在执行的命令,并且记录了详细的时间戳每个客户端都有自己的输出缓冲区,既然
monitor
能监听到所有的命令,则一旦并发量过大,则monitor
的输出缓存就会暴涨,瞬间占用大量内存,禁止在生产环境中使用monitor
命令
打开两个客户端,一个客户端A输入 monitor
127.0.0.1:6379> monitor
^
另一个客户端B输入命令,则客户端A可以监听到正在执行的命令,并且记录了详细的时间戳
客户端B
127.0.0.1:6379> keys *
1) "name"
2) "count"
3) "age"
4) "hello"
5) "qq"
127.0.0.1:6379> set m girl
OK
127.0.0.1:6379>
客户端A
127.0.0.1:6379> monitor
OK
1523876898.815455 [0 127.0.0.1:62628] "keys" "*"
1523876908.519187 [0 127.0.0.1:62628] "set" "m" "girl"
4、客户端常见异常分析,从源头上找
4.1、客户端链接超时
Connect time out
1、设置超时时间过短
2、网络不正常
3、Redis自身发生阻塞
4.2、无法从连接池池获取到连接
could not get a resourse
1、Jedis连接池连接个数是有限的,默认是8个,这里假设使用的默认配置,如果有8个对象被占用,并没有归还,此时还要借用,就需要等待(如果设置了maxWaitMIlls>0),如果超时,则会发生异常
2、连接池设置数量过小,并发量太大,8个也不够用
3、没有正确使用连接池,使用了8次,都没有释放
4、存在慢查询操作,这些慢查询持有的Jedis对象归还速度会比较慢,造成池子满了
4.3、客户端读写超时
Read time out
1、读写超时时间设置过段
2、命令本身比较慢
3、网络连接不正常
4、Redis自身发生阻塞
5.4、客户端缓冲区异常
unexpected end of stream
1、输出缓冲区满,例如将普通客户端的输出缓冲区设置成了 1M 1M 60
2、不正常的并发读写,
Jedis
对象呗多个线程并发操作
5.5、客户端连接数过大
如果客户端连接数超过了
maxclients
,就会抛出异常,遇到这个问题就麻烦了,因为肯定是不能再输入命令进行修复了
1、客户端方面,如果应用方是分布式结果的话,下线部分应用节点。让Redis连接数降下来,再通过查找程序bug或者调整maxclients进行修复
2、服务端,使用高可用模式主从复制和集群,将当前Redis做故障转移,这样别的就可以连接了
5.6、客户端案例分析
5.6.1、Redis内存陡增
服务端现象 :Redis主节点内存陡增,几乎用满maxmemory,而从节点没有变化(主从复制,内存使用主从节点基本相同)
客户端现象:客户端产生了OOM,也就是Redis主节点使用的内存已经超过了maxmemory,无法写入新的数据
1、原因和解决方案
1、确实有大量写入 ,通过查看主节点的dbseze和从节点的dbsize相同
2、输出缓冲区数量数量过大,造成内存陡增
3、查看到输出缓冲区数量过大
127.0.0.1:6379> info clients
# Clients
connected_clients:2
client_longest_output_list:225698
client_biggest_input_buf:0
blocked_clients:0
127.0.0.1:6379>
发现输出缓冲区的队列已经超过了20万个对象
2、通过 client list
找到不正常的连接
一般来说大部分客户端的omem
(输出缓存区占用字节为0,因为都已经执行完事了,只要找到不为omem=0的就代表找到了异常的链接),通过下面的查找发现最后一次执行的命令是monitor,很明显就知道是通过monitor造成的
redis-cli client list | grep -v “omem=0”
id=3822 addr=127.0.0.1:64244 fd=7 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=224551255844 events=r cmd=monitor
3、处理方法:只要使用 client kill
就可以杀掉这个连接,但是我们以后如果及时发现并且避免