我们都知道缓冲区是为了应对数据传递两端发送和接收速度不一致的方案。但如果缓冲区占用的资源超出设定的上限时,就会出现缓冲区溢出。
Redis是典型的客户端-服务端架构,在客户端与服务端通信过程中,缓冲区就是一个常用的方案。另外,在配置主从集群的Redis架构中,主从节点数据同步过程中,也用到了缓冲区。接下来我们看看客户端与服务端如果发生缓冲区溢出,我们该如何应对及规避。
客户端和服务端,往往配置及网络状况不一致。为了应对请求的发送及处理速度的不匹配,服务端有做这么一个操作,就是给每个连接的客户端设置输入缓冲区和输出缓冲区。
输入缓冲区会将客户端的发送命令暂存到缓冲区,Redis再从缓冲区取出命令来执行。当命令执行完成后,也不是直接的返回给客户端,也会将执行结果暂存到输出缓冲区,最后输出缓冲区将结果发送给客户端。画个图看看更好理解。
由上面概念我们得知输入缓冲区是用来暂存客户端的请求命令,很容易分析得出输入缓冲区溢出的情况:
如何解决输入缓冲区溢出问题呢?首先我们得知道缓冲区的资源使用问题。
我们可以用CLIENT LIST
来获取每个客户端的输入缓冲区使用情况。
127.0.0.1:6379> client list
id=6 addr=127.0.0.1:59256 fd=8 name= age=8 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
输出有很多信息,我们关注这几个数据:
通过这三个数据,我们能知道缓冲区的使用量及剩余空间。当发现qbuf-free很小时,就要注意缓冲区溢出的问题。这个时候再发送大量命令,就可能导致输入缓冲区溢出,Redis会将客户端连接直接关闭。
而且,当有多个客户端连接,输入缓冲区超过redis设置的maxmemory值时,就会触发Redis的另一机制—数据淘汰。更麻烦的是,如果整个Redis的内存占用过大,可能会导致内存溢出,这也就意味着Redis会崩溃。
为避免输入缓冲区溢出,我们一般能想到2个方法去应对:
第一个方法,因redis没有设置参数来调节,所以方法行不通。而且 源码设置的阈值是1GB,这个数值已经够大了,改源码的意义也不大。
因此,我们寄希望于第二个方法,避免bigkey的写入、避免Redis主线程的阻塞。
Redis为每个客户端有设置输出缓冲区,一部分是用来暂存OK响应和报错信息的,大小固定为16KB;还一部分是用来暂存大小可变的响应结果,它的大小是动态可变的。
分析一下输出缓冲区溢出的情况:
第一点很好理解,不用多说。再看看第二点,MONITOR是监测Redis的命令,执行之后会持续监测Redis的每个命令操作。因此,MONITOR使用越久,监测的结果就越多,空间自然约占越多。因此我们慎用MONITOR命令,尽量不在生产环境使用。
最后看看第三点,输出缓冲区的大小是可配置的,配置项是client-output-buffer-limit。
client-output-buffer-limit normal 0 0 0
| | | | - - - 持续写入时间限制
| | | - - - - 缓冲区持续写入量限制
| |- - - - - -缓冲区持续写入量限制
| - - - - - - - - 普通客户端
备注:normal代表普通客户端, 第一个0表示缓冲区大小限制,第二个0表示缓冲区持续写入量限制,第三个0表示持续写入时间限制。
配置时,我们首先要确认下客户端与服务端的交互方式。如果是普通客户端(命令读写),因为都是阻塞式的发送命令,经验上通常都设置为0;但如果是发布订阅模式的客户端,客户端和服务器间的消息发送方式不属于阻塞式发送,因此需要给缓冲区限制一下。
client-output-buffer-limit pubsub 8mb 2mb 60
如上配置,pubsub表示是订阅客户端,8mb表示缓冲区上限8MB,2mb 60表示60秒内连续写入到输出缓冲区的数据量超过2MB,服务端会关闭客户端连接。
本文总结了一下客户端与服务端缓冲区的知识以及如何避免缓冲区溢出的方法。熟悉这些知识,对我们排查Redis问题有很大的帮助。