Redis缓冲区分析
1 Redis缓冲区简介
缓冲区,用一块内存空间暂时存放命令数据,目的是解决因数据和命令的处理速度小于发送速度而导致数据丢失和性能问题。缓冲区的内存空间有限,当写数据速度>读数据速度持续进行,会导致缓冲区容量需越来越大。当缓冲区占用内存>设定上限阈值,就会出现缓冲区溢出,会丢数据。
危害
- 如果发生溢出就会丢失数据。
- 缓冲区大小设置不当可能因为缓冲区过大影响Redis实例所在机器的可用内存
缓冲区在redis中应用场景
1 用来暂存客户端发送的命令数据或者服务器端返回给客户端的数据结果。
2 在主从节点间进行数据同步时,用来暂存主节点接收的写命令和数据
2 客户端输入、输出缓冲区
redis服务器端和客户端之间的缓冲区。redis是典型的C/S结构,C即client,S即server。
为避免C、S 的请求发送和处理速度不匹配,S给每个连接的C都设个输入、输出缓冲区,称为客户端输入、输出缓冲区。输入缓冲区先暂存C发来的命令,Redis主线程再从中读命令并处理。
当Redis主线程处理完数据,会把结果写入输出缓冲区,再通过输出缓冲区返给客户端
2.1 输入缓冲区溢出
2.1.1 可能场景
写入bigkey,如一下写入多个百万级别的集合类型数据
服务器端处理请求速度过慢,如Redis主线程出现间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多
client list命令
查看输入缓冲区的内存使用,可使用CLIENT LIST命令:
CLIENT LIST
id=5 addr=127.0.0.1:50487 fd=9 name= age=4 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
案例展示的是一个客户端的输入缓冲区情况,如有多个客户端,输出结果中的addr会显示不同客户端的IP和端口号
输入缓冲区相关参数:
cmd
客户端最新执行的命令。这个例子中执行的是CLIENT命令。
qbuf
输入缓冲区已经使用的大小。这个例子中的CLIENT命令已使用了26字节大小的缓冲区。
qbuf-free
输入缓冲区尚未使用的大小。
这个例子中的CLIENT命令还可以使用32742字节的缓冲区。qbuf和qbuf-free的总和就是Redis服务器端当前为已连接的这个客户端分配的缓冲区总大小。这个例子中总共分配了 26 + 32742 = 32768字节,也就是32KB的缓冲区。
qbuf和qbuf-free:Redis为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时Redis会到输入缓冲区拉取命令并执行,输入缓冲区为客户端发送命令道Redis执行命令提供了缓冲功能。qbuf代表了输入缓冲区的大小,qbuf-free代表输入缓冲区的剩余容量。输入缓冲区会根据输入内容的大小动态调整,每个客户端的输入缓冲区大小不能超过1G。超过后客户端将被关闭
通常Redis S不止服务一个C,当多个C连接占用的内存总量,超过maxmemory配置项(如4G),触发Redis数据淘汰。如使用多个客户端,导致Redis内存占用过大,也会导致内存溢出(out-of-memory),进而引起Redis崩溃,给业务应用造成严重影响。
2.1.2 解决方案
Redis不允许我们调节客户端输入区的大小,Redis客户端的输入缓冲区的大小的上线阈值,在代码中设定为1GB。
因此必须避免客户端写入 bigkey,避免 Redis 主线程阻塞的慢命令。
2.2 输出缓冲区溢出
Redis输出缓冲区暂存Redis主线程要返回给客户端的数据。
一般主线程返回给客户端的数据,既有简单且大小固定的OK响应(例如,执行SET命令)或报错信息,也有大小不固定的、包含具体数据的执行结果(例如,执行HGET命令)。
因此,Redis为每个客户端设置的输出缓冲区,包括两部分:
一个16KB固定缓冲空间,暂存OK响应和出错信息
可动态增加的缓冲空间,暂存大小可变的响应结果
2.2.1 可能场景
bigkey
服务器端返回大量bigkey结果。
MONITOR
用来监测Redis执行的。执行这命令后,会持续输出监测到的各个命令操作:
MONITOR
OK
1600617456.437129 [0 127.0.0.1:50487] "COMMAND"
1600617477.289667 [0 127.0.0.1:50487] "info" "memory"
MONITOR输出结果会持续占用输出缓冲区,并越占越多,最后就是发生溢出。
MONITOR命令主要用在调试环境,生产环境禁止持续使用MONITOR。若线上偶尔使用MONITOR检查Redis命令执行情况,也没问题。
2.2.2 设置缓冲区 client-output-buffer-limit
设置缓冲区上限阈值:设置输出缓冲区持续写入数据的数量上限阈值,和持续写入数据的时间的上限阈值。
设置缓冲区大小前,需先区分客户端类型:普通客户端、发布订阅客户端、slave客户端
client-output-buffer-limit
选项:
class: 客户端类型 normal:普通客户端 ; slave:slave客户端,用于复制; pubsub:发布订阅客户端
hard limit:如果客户端使用的输出缓冲区大于hardlimit,客户端会立即关闭。
soft limit和soft seconds:如果客户端使用的输出缓冲区大于soft limit,并且超出了soft seconds秒,客户端会被立即关闭。
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
对于普通客户端来说,限制为0,也就是不限制。因为普通客户端通常采用阻塞式的消息应答模式,何谓阻塞式呢?如:发送请求,等待返回,再发送请求,再等待返回。这种模式下,通常不会导致Redis服务器输出缓冲区的堆积膨胀;
对于Pub/Sub客户端(也就是发布/订阅模式),大小限制是8M,当输出缓冲区超过8M时,会关闭连接。持续性限制是,当客户端缓冲区大小持续60秒超过2M,则关闭客户端连接;
对于slave客户端来说,大小限制是256M,持续性限制是当客户端缓冲区大小持续60秒超过64M,则关闭客户端连接。
2.2.2 解决方案
避免大K操作返回大量数据结果
避免在线上环境中持续使用MONITOR
使用client-output-buffer-limit设置合理缓冲区大小上限或缓冲区连续写入时间和写入量上限
3 主从集群中的缓冲区
全量复制和增量复制都会用到缓冲区。
3.1 全量复制缓冲区的溢出
全量复制,Master(后文简称为M)在向Replica(后文简称为R)传输RDB文件同时,会继续接收C发送的写请求。
这些写命令先保存在复制缓冲区,等RDB传输完,再发给从节点执行。
主节点会为每个从节点都维护一个复制缓冲区,保证和主从节点间的数据同步。
如果全量复制时,S接收和加载RDB较慢,同时M接收了大量的写命令,写命令在复制缓冲区中会越积越多,最终导致溢出,复制缓冲区一旦溢出,M也会直接关闭和R进行复制操作的连接,全量复制直接失败。
3.1.1 复制缓冲区发生溢出解决方案
1 控制主节点保存的数据量大小,建议把主节点的数据量控制在2-4GB,这样可以让全量同步执行更快些,避免复制缓冲区累积过多命令。
2 使用client-output-buffer-limit配置项,来设置合理的复制缓冲区大小。设置的依据就是主节点的数据量大小,主节点的数据量大小,主节点写负载压力和主节点本身的内存大小。
3 需要控制节点数量,避免主节点中复制缓冲区占用过多内存的问题。
在M执行:
config set client-output-buffer-limit replica 512mb 128mb 60
replica
该配置项针对复制缓冲区
512mb
将缓冲区大小的上限设为512M
128mb和60
若连续60s内写入量>128M,也会触发缓冲区溢出
假设一条写命令数据是1KB,则复制缓冲区可积压512K条(512MB/1KB = 512K)写命令。
M在全量复制期间,可承受写命令速率上限=2000条/s(128MB/1KB/60≈2000)。
评估依据:
根据写命令数据的大小 && 实际负载情况(即写命令速率),估计缓冲区中会积压的写命令数据量,再和所设置的复制缓冲区大小比较,判断设置的缓冲区是否够支撑积压的写命令数据量。由于M复制缓冲区的内存开销,会是每个R客户端输出缓冲区占用内存总和。若集群中R很多,M内存开销就很大。所以还得控制和M连接的R个数,不要使用大规模主从集群。
3.2 增量复制积压缓冲区的溢出
增量复制时使用的缓冲区,这个缓冲区称为复制积压缓冲区。
M在把接收到的写命令同步给R时,同时会把这些写命令写入复制积压缓冲区。
一旦R发生网络闪断,和M重连后,R就会从复制积压缓冲区读取断连期间M接收到的写命令,进行增量同步:
repl_backlog_buffer:复制积压缓冲区是一个大小有限的环形缓冲区。当主节点把复制积压缓冲区写满以后,会覆盖缓冲区中的旧命令。如果节点还没同步这些旧命令(通过对比offset),就会造成主从节点间重新开始执行全量复制。
为了应对复制冲压缓冲区的溢出问题,我们可以调整复制积压缓冲区的大小,即repl_backlog_size参数值。
4 总结
使用缓冲区后,当命令数据的接收方处理速度跟不上发送方的发送速度,缓冲区可避免丢失命令数据。
按缓冲区用途,如客户端通信or主从节点复制,分为:
客户端的输入和输出缓冲区
主从集群中主节点上的复制缓冲区和复制积压缓冲区
从缓冲区溢出对Redis的影响的角度,把四个缓冲区分成两类总结
缓冲区溢出导致网络连接关闭
普通客户端、订阅客户端及从节点客户端,所用缓冲区本质都是Redis客户端和服务器端间,或主从节点间为传输命令数据而维护。这些缓冲区一旦溢出,处理机制都是直接关闭客户端和服务器端的连接,或主从节点间的连接。
而网络连接关闭造成的直接影响,就是业务程序无法读写Redis,或者是主从节点全量同步失败,需重新执行。
缓冲区溢出导致命令数据丢失
M的复制积压缓冲区属环形缓冲区,一旦溢出,新写入的命令数据就会覆盖旧的命令数据,导致旧命令数据的丢失,进而导致主从节点重新全量复制。
缓冲区溢出的原因:
命令数据发送过快、过大
对普通客户端,可避免bigkey,而对复制缓冲区,就是避免过大RDB文件
命令数据处理较慢
减少Redis主线程上的阻塞操作,如使用异步删除操作
缓冲区空间过小
使用client-output-buffer-limit配置项设置合理的输出缓冲区、复制缓冲区和复制积压缓冲区大小
输入缓冲区的大小默认是固定的,无法通过配置修改,除非改源码。
Redis的主从同步非常依赖于两个参数的合理配置:
- client-output-buffer-limit
- repl-backlog-size
5 案例
数据变更量太大,超过了client-output-buffer-limit,会导致主从同步连接被断开,然后S要求psync,但是由于repl-backlog-size太小,导致psync失败,需要full sync,而full sync需要Discarding previously cached master state,重新load RDB文件到内存,而这个加载数据过程是阻塞式的。所以导致slave出现间歇式的不可用