缓冲区,用一块内存空间暂时存放命令数据,以免因
数据和命令的处理速度<发送速度
而导致数据丢失和性能问题。但缓冲区的内存空间有限,若持续:
往里写数据速度>从里读数据速度
会导致缓冲区需越来越多内存暂存数据。当缓冲区占用内存>设定上限阈值,就会出现缓冲区溢出。发生溢出,就会丢数据。不给缓冲区设上限,不就没这问题了?No!随累积数据增多,缓冲区所占内存空间越大,耗尽Redis机器可用内存时,Redis实例就会崩溃!
所以缓冲区是用来避免请求或数据丢失,使用姿势须正确,才能发挥作用。Redis所有操作命令都需通过C发给S。所以,缓冲区就是:
服务器端和客户端之间的缓冲区。
为避免C、S 的请求发送和处理速度不匹配,S给每个连接的C都设个输入、输出缓冲区,称为客户端输入、输出缓冲区。
可能溢出case:
要查看和Server相连的每个C对输入缓冲区的使用情况,可使用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
CLIENT命令返回信息虽多,只需关注:
有了CLIENT LIST命令,就可通过输出结果判断C输入缓冲区的内存占用情况: 若qbuf很大,qbuf-free很小,这时输入缓冲区已占用很多内存,而且没啥空闲空间。此时,C再写入大量命令,就会引起C输入缓冲区溢出,Redis就把C连接关闭,结果就是业务程序无法进行数据存取。
通常Redis S不止服务一个C,当多个C连接占用的内存总量,超过maxmemory配置项(如4G),触发Redis数据淘汰。一旦数据被淘汰,再要访问这部分数据,就需要去后端DB读,降低业务应用访问性能。如使用多个客户端,导致Redis内存占用过大,也会导致内存溢出(out-of-memory),进而引起Redis崩溃,给业务应用造成严重影响。
须避免输入缓冲区溢出,考虑:
有没有办法通过参数调整输入缓冲区的大小?没。Redis客户端输入缓冲区大小的上限阈值,在代码中就定为1G。即Redis服务器端允许为每个客户端最多暂存1G命令和数据。对一般生产环境已经合适:
所以,Redis并没有提供参数调节客户端输入缓冲区大小。如要避免输入缓冲区溢出,只能从数据命令的发送和处理速度入手,即避免客户端写入bigkey,以及避免Redis主线程阻塞。
Redis输出缓冲区暂存Redis主线程要返回给客户端的数据。 一般主线程返回给客户端的数据,既有简单且大小固定的OK响应(例如,执行SET命令)或报错信息,也有大小不固定的、包含具体数据的执行结果(例如,执行HGET命令)。
因此,Redis为每个客户端设置的输出缓冲区也包括两部分:
执行了MONITOR命令; 缓冲区大小设置得不合理。
服务器端返回大量bigkey结果。原本就会占用大量的内存空间,所以服务器端返回的结果包含bigkey,必影响输出缓冲区。
用来监测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命令执行情况,也没问题。
设置缓冲区大小:
设置缓冲区大小前,需先区分客户端类型。和Redis实例进行交互的应用程序,主要使用如下客户端:
给normal设置缓冲区大小时,可在redis.conf设置如下:
对于normal,它每发送完一个请求,会等到请求结果返回后,再发下一个请求-阻塞式发送。 这时,若非读取体量特大的大K,S输出缓冲区一般不会被阻塞。
所以,Redis默认把normal的缓冲区大小限制、持续写入量限制、持续写入时间限制都置0,即不限制。
一旦订阅的Redis频道有消息,S都会通过输出缓冲区把消息发给C。 所以,订阅C、S间的消息发送方式,不属阻塞式发送。 但若频道消息较多,也会占用较多输出缓冲区空间。
因此,要给订阅C设置缓冲区大小限制、缓冲区持续写入量限制及持续写入时间限制,Redis默认配置:
小结应对输出缓冲区溢出:
client-output-buffer-limit
设置合理缓冲区大小上限或缓冲区连续写入时间和写入量上限主从集群间的数据复制包括:
无论在哪种形式的复制,为保证主从节点数据一致,都会用到缓冲区。
全量复制,Master(后文简称为M)在向Replica(后文简称为R)传输RDB文件同时,会继续接收C发送的写请求。
这些写命令先保存在复制缓冲区,等RDB传输完,再发给从节点执行。
主节点会为每个从节点都维护一个复制缓冲区,保证和主从节点间的数据同步。
所以,若全量复制时,R接收和加载RDB较慢,同时M接收到大量写命令,写命令在复制缓冲区中就会积压,最终溢出。
M的复制缓冲区,其实也是个用于和R连接的客户端(称为从节点客户端),使用的输出缓冲区。复制缓冲区一旦溢出,M也会直接关闭和R进行复制操作的连接,全量复制直接失败。
client-output-buffer-limit
配置项设置合适复制缓冲区大小
设置依据:主节点的数据量大小、主节点的写负载压力和主节点本身的内存大小在M执行:
config set client-output-buffer-limit replica 512mb 128mb 60
假设一条写命令数据是1KB,则复制缓冲区可积压512K条(512MB/1KB = 512K)写命令。 M在全量复制期间,可承受写命令速率上限=2000条/s(128MB/1KB/60≈2000)。
这就得到一种方案,设置复制缓冲区大小时:
由于M复制缓冲区的内存开销,会是每个R客户端输出缓冲区占用内存的总和。若集群中的R很多,M内存开销就很大。所以还得控制和M连接的R个数,不要使用大规模主从集群。
为避免复制缓冲区积压过多命令造成溢出,导致全量复制失败,可:
增量复制时使用的缓冲区,这个缓冲区称为复制积压缓冲区。
M在把接收到的写命令同步给R时,同时会把这些写命令写入复制积压缓冲区。 一旦R发生网络闪断,和M重连后,R就会从复制积压缓冲区读取 断连期间 M接收到的写命令,进行增量同步:
repl_backlog_buffer。在缓冲区溢出角度来看:
使用缓冲区后,当命令数据的接收方处理速度跟不上发送方的发送速度,缓冲区可避免丢失命令数据。
按缓冲区用途,如客户端通信or主从节点复制,分为:
从缓冲区溢出对Redis的影响的角度,把四个缓冲区分成两类总结
缓冲区溢出的原因:
输入缓冲区的大小默认是固定的,无法通过配置修改,除非改源码。
客户端需使用缓冲区,好处:
无处不在,客户端缓冲区、服务端缓冲区、操作系统网络缓冲区等等,凡涉及数据交互的两端,一般都会使用缓冲区降低两端速度不匹配的影响。 没有缓冲区,就好比一个个工人搬运货物到目的地,每个工人不仅成本高,而且运输效率低。而有了缓冲区后,相当于把这些货物先装到一个集装箱里,然后以集装箱为单位,开车运送到目的地,这样既降低了成本,又提高了运输效率。 缓冲区相当于把需要运送的零散数据,进行一块块规整化,然后分批运输。
Redis服务端为客户端分配的输出缓冲区:主库上的从库输出缓冲区(slave client-output-buffer)是不计算在Redis使用的总内存,即主从同步延迟,数据积压在主库上的从库输出缓冲区中,这个缓冲区内存占用变大,不会超过maxmemory导致淘汰数据。 只有普通客户端和订阅客户端的输出缓冲区内存增长,超过maxmemory时,才会淘汰数据。
缓冲区类似队列,读写变成异步,主线程跟缓冲区交互,是什么线程负责缓冲区跟tcp的数据同步?
解决的虽都是速率不一致,缓冲问题:
tcp 的缓冲区面向的是网络的不可靠,redis 的缓冲区面向的是程序处理性能的不可靠。
tcp的缓冲区是系统内核维护的,负责tcp的可靠传输,确认机制,窗口大小,流量控制和拥塞控制等都需要缓冲区。redis的缓冲区是redis自己用,用于client-server机制,就是老师讲的。redis主线程发送数据时就是把自己缓存区的数据拷贝到内核的tcp缓冲区,之后由内核负责发送数据到网卡,内核是通过epoll机制知道有数据要发送的。