缓冲区

概述

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

Redis 是典型的client-server 架构,所有的操作命令都需要通过客户端发送给服务器端。所以,缓冲区在 Redis 中的一个主要应用场景,就是在客户端和服务器端之间进行通信时,用来暂存客户端发送的命令数据,或者是服务器端返回给客户端的数据结果。此外,缓冲区的另一个主要应用场景,是在主从节点间进行数据同步时,用来暂存主节点接收的写命令和数据

image-20230318172524755

输入缓冲区溢出

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

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

要查看和服务器端相连的每个客户端对输入缓冲区的使用情况,我们可以使用 CLIENT LIST 命令

重点关注两类信息:

  1. 一类是与服务器端连接的客户端的信息。
  2. 另一类是与输入缓冲区相关的三个参数:
    • cmd,表示客户端最新执行的命令。这个例子中执行的是 CLIENT 命令。
    • qbuf,表示输入缓冲区已经使用的大小。这个例子中的 CLIENT 命令已使用了 26 字节大小的缓冲区。
    • qbuf-free,表示输入缓冲区尚未使用的大小。这个例子中的 CLIENT 命令还可以使用 32742 字节的缓冲区。qbuf 和 qbuf-free 的总和就是,Redis 服务器端当前为已连接的这个客户端分配的缓冲区总大小。这个例子中总共分配了 26 + 32742 = 32768 字节,也就是 32KB 的缓冲区。

如何避免:

Redis 并没有提供参数让我们调节客户端输入缓冲区的大小。如果要避免输入缓冲区溢出,那我们就只能从数据命令的发送和处理速度入手,也就是前面提到的避免客户端写入 bigkey,以及避免 Redis 主线程阻塞。

输出缓冲区溢出

Redis 为每个客户端设置的输出缓冲区也包括两部分:

  • 一部分,是一个大小为 16KB 的固定缓冲空间,用来暂存 OK 响应和出错信息
  • 另一部分,是一个可以动态增加的缓冲空间,用来暂存大小可变的响应结果。

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

  • 服务器端返回 bigkey 的大量结果。

  • 执行了 MONITOR 命令。

    MONITOR 命令是用来监测 Redis 执行的。执行这个命令之后,就会持续输出监测到的各个命令操作。

    MONITOR 命令主要用在调试环境中,不要在线上生产环境中持续使用 MONITOR

  • 缓冲区大小设置得不合理。

    可以通过 client-output-buffer-limit 配置项,来设置缓冲区的大小;

    当我们给普通客户端设置缓冲区大小时,通常可以在 Redis 配置文件中进行这样的设置:client-output-buffer-limit normal 0 0 0或者client-output-buffer-limit pubsub 8mb 2mb 60

    第一种设置:

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

    第二种设置:

    其中,pubsub 参数表示当前是对订阅客户端进行设置;8mb 表示输出缓冲区的大小上限为 8MB,一旦实际占用的缓冲区大小要超过 8MB,服务器端就会直接关闭客户端的连接;2mb 和 60 表示,如果连续 60 秒内对输出缓冲区的写入量超过 2MB 的话,服务器端也会关闭客户端连接。

主从集群中的缓冲区

主从集群间的数据复制包括全量复制和增量复制两种。全量复制是同步所有数据,而增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。无论在哪种形式的复制中,为了保证主从节点的数据一致,都会用到缓冲区。

复制缓冲区的溢出问题

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

问题:如果在全量复制时,从节点接收和加载 RDB 较慢,同时主节点接收到了大量的写命令,写命令在复制缓冲区中就会越积越多,最终导致溢出。复制缓冲区一旦发生溢出,主节点也会直接关闭和从节点进行复制操作的连接,导致全量复制失败。

解决:我们可以控制主节点保存的数据量大小,并使用 client-output-buffer-limit 配置项,来设置合理的复制缓冲区大小:config set client-output-buffer-limit slave 512mb 128mb 60,最后我们需要控制从节点的数量,来避免主节点中复制缓冲区占用过多内存的问题。

其中,slave 参数表明该配置项是针对复制缓冲区的。512mb 代表将缓冲区大小的上限设置为 512MB;128mb 和 60 代表的设置是,如果连续 60 秒内的写入量超过 128MB 的话,也会触发缓冲区溢出。

复制积压缓冲区的溢出问题

我们再来看下增量复制时使用的缓冲区,这个缓冲区称为复制积压缓冲区。

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

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

解决:为了应对复制积压缓冲区的溢出问题,我们可以调整复制积压缓冲区的大小,也就是设置 repl_backlog_size 这个参数的值。