Redis 客户端

本文详细介绍了Redis客户端管理,包括API如client list、client kill、client pause和monitor的使用,以及客户端相关配置和统计信息。同时,文章讨论了客户端常见异常,如连接超时、读写超时、缓冲区异常等问题,分析了原因并提供了处理方法。最后,通过实际案例分析了Redis内存陡增和客户端周期性超时的问题,提出了解决和预防措施。

1. 客户端管理

  Redis 提供了客户端相关 API 对其状态进行监控和管理,这里将深入介绍各个 API 的使用方法以及在开发运维中可能遇到的问题。

  

1.1 客户端 API

1.1.1 client list

  client list 命令能列出与 Redis 服务端相连的所有客户端连接信息,例如下面代码是在一个 Redis 实例上执行 client list 的结果:

127.0.0.1:6379> client list
id=254487 addr=10.2.xx.234:60240 fd=1311 name= age=8888581 idle=8888581 flags=N
db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get 

id=300210 addr=10.2.xx.215:61972 fd=3342 name= age=8054103 idle=8054103 flags=N
db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get 

id=5448879 addr=10.16.xx.105:51157 fd=233 name= age=411281 idle=331077 flags=N
db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ttl 

id=2232080 addr=10.16.xx.55:32886 fd=946 name= age=603382 idle=331060 flags=N
db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=get 

id=7125108 addr=10.10.xx.103:33403 fd=139 name= age=241 idle=1 flags=N db=0
sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=del 

id=7125109 addr=10.10.xx.101:58658 fd=140 name= age=241 idle=1 flags=N db=0
sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=del
...

  输出结果的每一行代表一个客户端的信息,可以看到每行包含了十几个属性,它们是每个客户端的一些执行状态,理解这些属性对于 Redis 的开发和运维人员非常有帮助:

  

1.1.1.1 标识:id、addr、fd、name

这四个属性属于客户端的标识:

  • id:客户端连接的唯一标识,这个id是随着 Redis 的连接自增的,重启 Redis 后会重置为0;
  • addr:客户端连接的ip和端口;
  • fd:socket 的文件描述符,如果 fd=-1 代表当前客户端不是外部客户端,而是 Redis 内部的伪装客户端;
  • name:客户端的名字,后面的 client setName 和 client getName 两个命令会对其进行说明

  

1.1.1.2 输入缓冲区:qbuf、qbuf-free

  Redis 为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时 Redis 从会输入缓冲区拉取命令并执行,输入缓冲区为客户端发送命令到 Redis 执行命令提供了缓冲功能

在这里插入图片描述
  

  • client list 中 qbuf 和 qbuf-free 分别代表这个缓冲区的总容量和剩余容量;
  • Redis 没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过 1G,超过后客户端将被关闭。

  
输入缓冲使用不当会产生两个问题:

  • 一旦某个客户端的输入缓冲区超过1G,客户端将会被关闭;
  • 输入缓冲区不受 maxmemory 控制,假设一个 Redis 实例设置了 maxmemory 为 4G,已经存储了2G数据,但是如果此时输入缓冲区使用了 3G,已经超过 maxmemory 限制,可能会产生数据丢失、键值淘汰、OOM等情况。

  

  输入缓冲区使用不当造成的危害非常大,那么造成输入缓冲区过大的原因有哪些?

  • 输入缓冲区过大主要是因为 Redis 的处理速度跟不上输入缓冲区的输入速度,并且每次进入输入缓冲区的命令包含了大量 bigkey,从而造成了输入缓冲区过大的情况;
  • 还有一种情况就是Redis发生了 阻塞,短期内不能处理命令,造成客户端输入的命令积压在了输入缓冲区, 造成了输入缓冲区过大。
      

那么如何快速发现和监控呢?监控输入缓冲区异常的方法有两种:

  • 通过定期执行 client list 命令,收集 qbuf 和 qbuf-free 找到异常的连接记录并分析,最终找到可能出问题的客户端;
  • 通过 info 命令的 info clients 模块,找到最大的输入缓冲区,client_biggest_input_buf 代表最大的输入缓冲区,例如可以设置超过10M就进行报警。

  对比client list和info clients监控输入缓冲区的优劣势

在这里插入图片描述

注意⚠️
输入缓冲区问题出现概率比较低,但是也要做好防范,在开发中要减少 bigkey、减少 Redis 阻塞、合理的监控报警

  

1.1.1.3 输出缓冲区:obl、oll、omem

  Redis 为每个客户端分配了输出缓冲区,它的作用是保存命令执行的结果返回给客户端,为 Redis 和客户端交互返回结果提供缓冲。

在这里插入图片描述

  与输入缓冲区不同的是,输出缓冲区的容量可以通过参数 client-output- buffer-limit 来进行设置,并且输出缓冲区做得更加细致,按照客户端的不同分为三种:普通客户端、发布订阅客户端、slave客户端

  

对应的配置规则是:

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
  • :客户端类型,分为三种。a)normal:普通客户端;b) slave:slave客户端,用于复制;c)pubsub:发布订阅客户端;
  • :如果客户端使用的输出缓冲区大于 ,客户端会被立即关闭;
  • 和:如果客户端使用的输出缓冲区超过了 并且持续了 秒,客户端会被立即关闭;

Redis 的默认配置是:

client-output-buffer-limit normal 0 0 0 
client-output-buffer-limit slave 256mb 64mb 60 
client-output-buffer-limit pubsub 32mb 8mb 60

  和输入缓冲区相同的是,输出缓冲区也不会受到 maxmemory 的限制,如果使用不当同样会造成 maxmemory 用满产生的数据丢失、键值淘汰、OOM等情况。

  实际上输出缓冲区由两部分组成:固定缓冲区(16KB)和动态缓冲区,其中固定缓冲区返回比较小的执行结果,而动态缓冲区返回比较大的结果,例如大的字符串、hgetall、smembers 命令的结果等。

  固定缓冲区使用的是字节数组,动态缓冲区使用的是列表。当固定缓冲区存满后会将 Redis 新的返回结果存放在动态缓冲区的队列中,队列中的每个对象就是每个返回结果。

在这里插入图片描述

  client list 中的 obl 代表固定缓冲区的长度,oll 代表动态缓冲区列表的长度,omem 代表使用的字节数

# 代表当前客户端的固定缓冲区的长 度为0,动态缓冲区有4869个对象,
# 两个部分共使用了133081288字节=126M 内存:

id=7 addr=127.0.0.1:56358 fd=6 name= age=91 idle=0 flags=O db=0 sub=0 psub=0 
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=4869 omem=133081288 events=rw cmd=monitor

  
监控输出缓冲区的方法依然有两种:

  • 通过定期执行 client list 命令,收集 obl、oll、omem 找到异常的连接记录并分析,最终找到可能出问题的客户端;
  • 通过 info 命令的 info clients 模块,找到输出缓冲区列表最大对象数,client_longest_output_list 代表输出缓冲区列表最大对象数。

  

  相比于输入缓冲区,输出缓冲区出现异常的概率相对会比较大,那么如何预防呢?方法如下:

  • 进行上述监控,设置阀值,超过阀值及时处理;
  • 限制普通客户端输出缓冲区的,把错误扼杀在摇篮中,例如可以进行如下设置
client-output-buffer-limit normal 20mb 10mb 120
  • 适当增大 slave 的输出缓冲区的,如果 master 节点写入较大,slave 客户端的输出缓冲区可能会比较大,一旦 slave 客户端连接因为输出缓冲区溢出被 kill,会造成复制重连;
  • 限制容易让输出缓冲区增大的命令,例如,高并发下的 monitor 命令就 是一个危险的命令;
  • 及时监控内存,一旦发现内存抖动频繁,可能就是输出缓冲区过大。

  

1.1.1.4 客户端的存活状态

  client list 中的 age 和 idle 分别代表当前客户端已经连接的时间和最近一次的空闲时间:

id=2232080 addr=10.16.xx.55:32886 fd=946 name= age=603382 idle=331060

  如果当期客户端连接 Redis 的时间为 8888581 秒,其中空闲了 8888581 秒,实际上这种就属于不太正常的情况,当age等于idle时, 说明连接一直处于空闲状态

  

1.1.1.5 客户端的限制 maxclients 和 timeout

  Redis 提供了 maxclients 参数来限制最大客户端连接数,一旦连接数超过 maxclients,新的连接将被拒绝。maxclients 默认值是 10000,可以通过 info clients 来查询当前 Redis 的连接数

127.0.0.1:6379> info clients 
# Clients 
connected_clients:1414
...

  

1.1.1.6 客户端类型

  lient list 中的 flag 是用于标识当前客户端的类型,例如 flag=S 代表当前客户端是 slave 客户端、flag=N 代表当前是普通客户端,flag=O 代表当前客户端正在执行monitor命令。

在这里插入图片描述

  

  

1.1.2 client kill

  此命令用于杀掉指定IP地址和端口的客户端

client kill ip:port

  

1.1.3 client pause

  client pause 命令用于阻塞客户端 timeout 毫秒数,在此期间客户端连接将被阻塞

client pause timeout(毫秒)

  

1.1.4 monitor

  monitor 命令用于监控 Redis 正在执行的命令,monitor 的作用很明显,如果开发和运维人员想监听 Redis 正在执行的命令,就可以用 monitor 命令,但事实并非如此美好,每个客户端都有自己的输出缓冲区,既然 monitor 能监听到所有的命令,一旦 Redis 的并发量过大, monitor 客户端的输出缓冲会暴涨,可能瞬间会占用大量内存。

  

  

1.2 客户端相关配置

  • timeout:检测客户端空闲连接的超时时间,一旦 idle 时间达到了 timeout,客户端将会被关闭,如果设置为0就不进行检测;
  • maxclients:客户端最大连接数;
  • tcp-keepalive:检测 TCP 连接活性的周期,默认值为0,也就是不进行检测,如果需要设置,建议为 60,那么 Redis 会每隔 60秒对它创建的 TCP 连接进行活性检测,防止大量死连接占用系统资源。

  

  

1.3 客户端统计片段

1.3.1 info clients

下面就是一次info clients的执行结果:

127.0.0.1:6379> info clients
# Clients
connected_clients:1414 
client_longest_output_list:0 
client_biggest_input_buf:2097152 
blocked_clients:0

说明如下:

  • connected_clients:代表当前Redis节点的客户端连接数,需要重点监控,一旦超过 maxclients,新的客户端连接将被拒绝;
  • client_longest_output_list:当前所有输出缓冲区中队列对象个数的最大值;
  • client_biggest_input_buf:当前所有输入缓冲区中占用的最大容量;
  • blocked_clients:正在执行阻塞命令 (例如blpop、brpop、 brpoplpush) 的客户端个数;

  

1.3.2 info stats

# Stats 
total_connections_received:80 
...
rejected_connections:0

参数说明:

  • otal_connections_received:Redis 自启动以来处理的客户端连接数总数;
  • rejected_connections:Redis 自启动以来拒绝的客户端连接数,需要重点监控。
      

  

2. 客户端常见异常

2.1 无法从连接池获取到连接

  连接池对象被占用,并且没有归还,此时调用者还要从池中借用,就需要进行等待 (例如设置了maxWaitMillis>0) ,如 果在 maxWaitMillis 时间内仍然无法获取到对象就会抛出如下异常:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
...
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.
java:449)

  还有一种情况,就是设置了 blockWhenExhausted=false,那么调用者发现池子中没有资源时,会立即抛出异常不进行等待,下面的异常就是 blockWhenExhausted=false 时的效果:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
...
Caused by: java.util.NoSuchElementException: Pool exhausted
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.
java:464)

  对于这个问题,需要重点讨论的是为什么连接池没有资源了,造成没有资源的原因非常多,可能如下:

  1. 客户端:高并发下连接池设置过小,出现供不应求,所以会出现上面的错误,但是正常情况下只要比默认的最大连接数(8个)多一些即可,因 为正常情况下JedisPool 以及 Jedis 的处理效率足够高;
  2. 客户端:没有正确使用连接池,比如没有进行释放;
  3. 客户端:存在慢查询操作,这些慢查询持有的 Jedis 对象归还速度会比较慢,造成池子满了;
  4. 服务端:客户端是正常的,但是 Redis 服务端由于一些原因造成了客户端命令执行过程的阻塞,也会使得客户端抛出这种异常。

  

2.2 客户端读写超时

  Jedis 在调用 Redis 时,如果出现了读写超时后,会出现下面的异常:

redis.clients.jedis.exceptions.JedisConnectionException:
java.net.SocketTimeoutException: Read timed out

造成该异常的原因也有以下几种:

  1. 读写超时间设置得过短;
  2. 命令本身就比较慢;
  3. 客户端与服务端网络不正常;
  4. Redis自身发生阻塞。

  

2.3 客户端连接超时

  Jedis 在调用 Redis 时,如果出现了连接超时后,会出现下面的异常

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out

造成该异常的原因也有以下几种:

  1. 连接超时设置得过短;
  2. Redis 发生阻塞,造成 tcp-backlog 已满,造成新的连接失败;
  3. 客户端与服务端网络不正常。

  

2.4 客户端缓冲区异常

  Jedis 在调用 Redis 时,如果出现客户端数据流异常,会出现下面的异常:

redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream

造成这个异常的原因可能有如下几种:

  1. 输出缓冲区满。例如将普通客户端的输出缓冲区设置为1M 1M 60,如果使用 get 命令获取一个 bigkey (例如3M),就会出现这个异常;
  2. 长时间闲置连接被服务端主动断开;
  3. 不正常并发读写:Jedis 对象同时被多个线程并发操作,可能会出现上述异常。

  

2.5 Redis正在加载持久化文件

  Jedis 调用 Redis 时,如果 Redis 正在加载持久化文件,那么会收到下面的
异常:

redis.clients.jedis.exceptions.JedisDataException: 
						LOADING Redis is loading the dataset in memory

  

2.6 Redis 使用的内存超过 maxmemory 配置

  Jedis 执行写操作时,如果 Redis 的使用内存大于 maxmemory 的设置,会
收到下面的异常,此时应该调整 maxmemory 并找到造成内存增长的原因:

redis.clients.jedis.exceptions.JedisDataException: 
		OOM command not allowed when used memory > 'maxmemory'.

  

2.7 客户端连接数过大

  如果客户端连接数超过了maxclients,新申请的连接就会出现如下异常:

redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached

  此时新的客户端连接执行任何命令,返回结果都是如下:

127.0.0.1:6379> get hello
(error) ERR max number of clients reached

  这个问题可能会比较棘手,因为此时无法执行 Redis 命令进行问题修 复,一般来说可以从两个方面进行着手解决:

  • 客户端:如果 maxclients 参数不是很小的话,应用方的客户端连接数基本不会超过maxclients,通常来看是由于应用方对于 Redis 客户端使用不当造成的。此时如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用连接较多的节点),使得 Redis 的连接数先降下来。从而让绝大部分节点可以正常运行,此时再通过查找程序 bug 或者调整 maxclients进行问题的修复;
  • 服务端:如果此时客户端无法处理,而当前 Redis 为高可用模式 (例如 Redis Sentinel 和 Redis Cluster),可以考虑将当前 Redis 做故障转移。

  

  

3. 客户端案例分析

3.1 Redis 内存陡增

3.1.1 现象

  1. 服务端现象
      Redis 主节点内存陡增,几乎用满 maxmemory,而从节点内存并没有变化

在这里插入图片描述
2. 客户端现象
  客户端产生了 OOM异常,也就是 Redis 主节点使用的内存已经超过了 maxmemory 的设置,无法写入新的数据

redis.clients.jedis.exceptions.JedisDataException: 
		OOM command not allowed when used memory > 'maxmemory'

  

3.1.2 分析原因

从现象看,可能的原因有两个:

  1. 确实有大量写入,但是主从复制出现问题:查询了 Redis 复制的相关信息,复制是正常的,主从数据基本一致
# 主节点的键个数:
127.0.0.1:6379> dbsize 
(integer) 2126870

# 从节点的键个数
127.0.0.1:6380> dbsize 
(integer) 2126870
  1. 其他原因造成主节点内存使用过大:排查是否由客户端缓冲区造成主节点内存陡增,使用 info clients 命令查询相关信息如下:
127.0.0.1:6379> info clients
# Clients
connected_clients:1891 
client_longest_output_list:225698 
client_biggest_input_buf:0 
blocked_clients:0

  很明显输出缓冲区不太正常,最大的客户端输出缓冲区队列已经超过了 20万个对象,于是需要通过 client list 命令找到 omem 不正常的连接,一般来说大部分客户端的 omem为0 (因为处理速度会足够快),于是执行如下代码,找到 omem 非零的客户端连接:

redis-cli client list | grep -v "omem=0"

找到了如下一条记录:

id=7 addr=10.10.xx.78:56358 fd=6 name= age=91 idle=0 flags=O db=0 sub=0 psub=0 
multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=224869 omem=2129300608 events=rw 
cmd=monitor

  已经很明显是因为有客户端在执行 monitor 命令造成的。

  

3.1.3 处理方法和后期处理

  对这个问题处理的方法相对简单,只要使用 client kill 命令杀掉这个连接,让其他客户端恢复正常写数据即可。但是更为重要的是在日后如何及时 发现和避免这种问题的发生,基本有三点:

  1. 从运维层面禁止 monitor 命令,例如使用 rename-command 命令重置 monitor 命令为一个随机字符串,除此之外,如果 monitor 没有做 rename- command,也可以对 monitor 命令进行相应的监控 (例如client list);
  2. 从开发层面进行培训,禁止在生产环境中使用 monitor 命令,因为有时候 monitor 命令在测试的时候还是比较有用的,完全禁止也不太现实;
  3. 限制输出缓冲区的大小;
  4. 使用专业的 Redis 运维工具,提供相应的报警,快速发现和定位问题。

  

  

3.2 客户端周期性的超时

3.2.1 现象

  1. 客户端现象
      客户端出现大量超时,经过分析发现超时是周期性出现的,这为问题的查找提供了重要依据:
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: 
		java.net. SocketTimeoutException: connect timed out
  1. 服务端现象
      服务端并没有明显的异常,只是有一些慢查询操作。

  

3.2.2 分析

  1. 网络原因:服务端和客户端之间的网络出现周期性问题,经过观察网络是正常的;
  2. Redis本身:经过观察 Redis 日志统计,并没有发现异常;
  3. 客户端:由于是周期性出现问题,就和慢查询日志的历史记录对应了一下时间,发现只要慢查询出现,客户端就会产生大量连接超时,两个时间点基本一致。
      

  最终找到问题是慢查询操作造成的,通过执行 hlen 发现有200万个元 素,这种操作必然会造成 Redis 阻塞,通过与应用方沟通了解到他们有个定时任务,每5分钟执行一次hgetall操作

127.0.0.1:6399> hlen user_fan_hset_sort 
(integer) 2883279

  

3.2.3 处理方法和后期处理

  这个问题处理方法相对简单,只需要业务方及时处理自己的慢查询即
可,但是更为重要的是在日后如何及时发现和避免这种问题的发生,基本有三点:

  1. 从运维层面,监控慢查询,一旦超过阀值,就发出报警;
  2. 从开发层面,加强对于 Redis 的理解,避免不正确的使用方式;
  3. 使用专业的Redis运维工具,提供相应的报警,快速发现和定位问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值