Redis学习笔记21——缓冲区:一个可能引发“惨案”的地方

Posted qq_34132502

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis学习笔记21——缓冲区:一个可能引发“惨案”的地方相关的知识,希望对你有一定的参考价值。

缓冲区的功能其实很简单,主要就是用一块内存空间来暂时存放命令数据,以免出现因为数据和命令的处理速度慢于发送速度而导致的数据丢失和性能问题。

但如果写入速度持续大于读取速度,则缓冲区必然溢出。

能否无限制的扩大缓冲区?

显然不行,因为缓冲区处于内存,而Redis也在内存中,如果持续扩大缓冲区,很可能导致内存空间不足,从而使得Redis实例崩溃。

在cs架构中,Redis与客户端进行通信之间需要输入缓冲区暂存客户端发送的命令数据,以及输出缓冲器暂存服务器返回给客户端的数据结果。

此外,在主从节点间进行数据同步时,用来暂存主节点接受的的写命令和数据。

客户端缓冲区

为了避免客户端和服务器端的请求发送和处理速度不匹配,服务器端给每个连接的客户端都设置了一个输入缓冲区和输出缓冲区。

输入缓冲区溢出

可能导致溢出的情况主要是下面两种:

  • 写入了 bigkey,比如一下子写入了多个百万级别的集合类型数据;
  • 服务器端处理请求的速度过慢,例如,Redis 主线程出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多。

我们可以从两个角度去考虑如何避免,一是把缓冲区调大,二是从数据命令的发送和处理速度入手。

但是Redis没有提供使用参数修改输入缓冲区大小的参数,如果需要修改的话就需要修改Redis源码。在源码中将其设定为1GB。

所以我们只能避免客户端写入bigkey,以及避免 Redis 主线程阻塞。

输出缓冲区溢出

可能导致溢出的情况:

  • 服务器端返回 bigkey 的大量结果;
  • 执行了 MONITOR 命令;
  • 缓冲区大小设置得不合理。

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。

接下来,我们看下输出缓冲区大小设置的问题

具体设置的内容包括两方面:

  • 设置缓冲区大小的上限阈值;
  • 设置输出缓冲区持续写入数据的数量上限阈值,和持续写入数据的时间的上限阈值。

可以使用client-output-buffer-limit 来设置缓冲区大小

在设置缓冲区大小的时候,需要区分一下客户端类型:

  • 常规和 Redis 服务器端进行读写命令交互的普通客户端
  • 订阅了 Redis 频道的订阅客户端

当我们给普通客户端设置缓冲区大小时,通常可以在 Redis 配置文件中进行这样的设置:

client-output-buffer-limit normal 0 0 0

normal 表示当前设置的是普通客户端,第 1 个 0 设置的是缓冲区大小限制,第 2 个 0 和第 3 个 0 分别表示缓冲区持续写入量限制和持续写入时间限制。

对于普通客户端来说,它每发送完一个请求,会等到请求结果返回后,再发送下一个请求,这种发送方式称为阻塞式发送。在这种情况下,如果不是读取体量特别大的 bigkey,服务器端的输出缓冲区一般不会被阻塞的。
所以通常对普通用户不做大小限制。

而对于订阅客户端来说,一旦订阅的 Redis 频道有消息了,服务器端都会通过输出缓冲区把消息发给客户端。所以,订阅客户端和服务器间的消息发送方式,不属于阻塞式发送。如果频道消息较多的话,会占用较多的输出缓冲区空间。因此我们需要对其进行限制。

pubsub 表示当前是对订阅客户端进行设置

client-output-buffer-limit pubsub 8mb 2mb 60

主从集群的缓冲区

主从集群间的数据复制包括全量复制和增量复制两种。这两种复制场景下的缓冲区,在溢出影响和大小设置方面并不一样。

复制缓冲区溢出

在全量复制过程中,主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求。这些写命令就会先保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。主节点上会为每个从节点都维护一个复制缓冲区,来保证主从节点间的数据同步。

所以,如果在全量复制时,从节点接收和加载 RDB 较慢,同时主节点接收到了大量的写命令,写命令在复制缓冲区中就会越积越多,最终导致溢出。

复制缓冲区一旦发生溢出,主节点也会直接关闭和从节点进行复制操作的连接,导致全量复制失败。

所以,一方面,我们可以控制主节点保存的数据量大小。按通常的使用经验,我们会把主节点的数据量控制在 2~4GB,这样可以让全量同步执行得更快些,避免复制缓冲区累积过多命令。

另一方面,我们可以使用 client-output-buffer-limit 配置项,来设置合理的复制缓冲区大小。

slave表示该配置项是针对复制缓冲区的

config set client-output-buffer-limit slave 512mb 128mb 60

增量缓冲区溢出(repl_backlog_buffer)

主节点在把接收到的写命令同步给从节点时,同时会把这些写命令写入复制积压缓冲区。一旦从节点发生网络闪断,再次和主节点恢复连接后,从节点就会从复制积压缓冲区中,读取断连期间主节点接收到的写命令,进而进行增量同步

首先,复制积压缓冲区是一个大小有限的环形缓冲区。当主节点把复制积压缓冲区写满后,会覆盖缓冲区中的旧命令数据。如果从节点还没有同步这些旧命令数据,就会造成主从节点间重新开始执行全量复制。

其次,为了应对复制积压缓冲区的溢出问题,我们可以调整复制积压缓冲区的大小,也就是设置 repl_backlog_size 这个参数的值。如果设置过小会导致经常进行全量复制。

小结

从缓冲溢出的影响来看,这四个缓冲区可以分两类:

  • 缓冲区溢出导致网络连接关闭:普通客户端、订阅客户端,以及从节点客户端,它们使用的缓冲区,本质上都是 Redis 客户端和服务器端之间,或是主从节点之间为了传输命令数据而维护的。这些缓冲区一旦发生溢出,处理机制都是直接把客户端和服务器端的连接,或是主从节点间的连接关闭。网络连接关闭造成的直接影响,就是业务程序无法读写 Redis,或者是主从节点全量同步失败,需要重新执行。
  • 缓冲区溢出导致命令数据丢失:主节点上的复制积压缓冲区属于环形缓冲区,一旦发生溢出,新写入的命令数据就会覆盖旧的命令数据,导致旧命令数据的丢失,进而导致主从节点重新进行全量复制。

缓冲区溢出,总的来说只有三个原因:

  • 数据发送过快过大:对于普通客户端来说可以避免 bigkey,而对于复制缓冲区来说,就是避免过大的 RDB 文件。
  • 数据处理过慢:解决方案就是减少 Redis 主线程上的阻塞操作,例如使用异步的删除操作。
  • 缓冲区过小:使用 client-output-buffer-limit 配置项设置合理的输出缓冲区、复制缓冲区和复制积压缓冲区大小。(除了输入缓冲区)

以上是关于Redis学习笔记21——缓冲区:一个可能引发“惨案”的地方的主要内容,如果未能解决你的问题,请参考以下文章

学习笔记-Redis设计与实现-链表

Redis学习笔记之多机数据库

Redis Sentinel 学习笔记

由Redis Cluster集群引发的对几种算法的思考

由Redis Cluster集群引发的对几种算法的思考

Netty学习3(学习笔记)