redis 8.0-M03版本提供了RDB 通道复制功能。
水平及维护精力所限,译文不免存在错误或过时之处,如有疑问,请查阅原文(文末英文链接)。
动机
在全量同步期间,当主节点向从节点传递 RDB 文件时,传入的写命令会被暂存在复制缓冲区中,以便在 RDB文件 传输完成后发送给从节点。如果 RDB文件传输耗时过长,可能会给主节点带来内存压力。此外,一旦从节点连接累积的复制数据超过了输出缓冲区的限制,主节点将断开与从节点的连接。这可能会导致复制失败。
RDB 通道复制的主要优势在于在传输 RDB文件的同时并行传输传入写命令。这种方法将复制流的缓冲工作转移到从节点,从而减轻主节点的负载。我们通过为 RDB文件传输打开另一个连接来实现这一点。从节点的主通道将接收复制流,而 RDB 通道则负责接收 RDB 文件。
这一特性还有助于降低主节点主进程的 CPU 负载。通过为 RDB文件传输打开专用连接,`bgsave` 进程可以访问新连接,并直接将 RDB 文件流式传输到从节点。在此更改之前,由于 TLS 连接的限制,`bgsave` 进程需要将 RDB 数据写入管道,然后由主进程转发给从节点。现在这一步骤已不再必要,主进程可以避免这些昂贵的套接字读写系统调用。这也意味着 RDB 文件传输到从节点的速度会更快,因为它省略了这一步骤。
总之,复制过程将更快,主节点在全量同步期间的性能将得到提升。
实现步骤
当从节点连接到主节点时,它会发送 `rdb-channel-repl`,以告知主节点从节点支持 RDB 通道。
当从节点缺少足够的数据进行 PSYNC 时,主节点会发送 `+RDBCHANNELSYNC` 响应,并附带从节点的客户端 ID。接下来,从节点会打开一个新的连接(RDB 通道),并使用适当的能力和要求配置该连接,以与主节点建立连接。它还会通过 RDB 通道将给定的客户端 ID 发送回主节点,以便主节点将这些通道关联起来。(初始的从节点连接将被称为 `main-channel`)。然后,从节点使用 RDB 通道请求全量同步。
在 fork 操作之前,主节点会将从节点的主通道附加到复制回放日志,以便从快照结束偏移量开始传递复制流。
主节点的主进程通过主通道发送复制流,而 `bgsave` 进程则通过 RDB 通道直接将 RDB 文件发送给从节点。从节点在本地缓冲区中累积复制流,同时将 RDB 文件加载到内存中。
一旦从节点完成 RDB 文件的加载,它会关闭 RDB 通道,并将累积的复制流加载到数据库中。同步完成。
一些细节
目前,只有在主节点启用了 `repl-diskless-sync` 时,才支持 RDB 通道复制。否则,复制将通过单个连接进行,就像之前一样。
在从节点上,对复制流缓冲区有限制。从节点使用新的配置 `replica-full-sync-buffer-limit` 来限制累积的字节数。如果未设置该配置,从节点将继承 `<replica>` 的 `client-output-buffer-limit` 硬限制配置。如果我们达到这个限制,从节点将停止累积。但这并不是一个失败场景。进一步的累积将在主节点上进行。根据主节点上配置的限制,主节点可能会断开与从节点的连接。
INFO 输出中的 API 变更
1、新的副本状态:send_bulk_and_stream。表示此副本的全量同步仍在进行中。它正在并行接收复制流和 RDB 文件。
slave0:ip=127.0.0.1,port=5002,state=send_bulk_and_stream,offset=0,lag=0
副本状态按步骤变化:
(1)首先,副本发送 `psync` 命令并接收 `+RDBCHANNELSYNC` 响应:`state=wait_bgsave`(等待 `bgsave`)
(2)当副本通过 RDB 通道建立连接并开始传输时:`state=send_bulk_and_stream`(正在发送批量数据和流数据)
(3)全量同步完成后:`state=online`(在线)
2、在副本端,复制流缓冲区的指标
(1) `replica_full_sync_buffer_size`:当前累积的复制流数据(以字节为单位)。
(2)`replica_full_sync_buffer_peak`:此实例在其生命周期内累积的最大字节数。
replica_full_sync_buffer_size:20485
replica_full_sync_buffer_peak:1048560
CLIENT LIST 输出中的 API 变更
在客户端列表输出中,RDB 通道客户端除了 'S' 副本标志外,还会有一个 'C' 标志:
id=11 addr=127.0.0.1:39108 laddr=127.0.0.1:5001 fd=14 name= age=5 idle=5 flags=SC db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1920 events=r cmd=psync user=default redir=-1 resp=2 lib-name= lib-ver= io-thread=0
配置变更
(1)`replica-full-sync-buffer-limit`:控制副本在 RDB 通道复制期间可以累积的复制数据量。如果未设置该配置项,则值为 0,表示副本将继承 `<replica>` 的 `client-output-buffer-limit` 硬限制配置,以限制累积的数据量。
(2)添加了 `repl-rdb-channel` 配置项,作为隐藏配置。这主要是为了测试,因为我们需要同时支持 RDB 通道复制和旧的单连接复制(以保持与旧版本的兼容性,如果未启用 `repl-diskless-sync`,则不会启用 RDB 通道复制)。该配置项会影响主节点(不响应 RDB 通道请求)和副本节点(不声明该能力)。
内部 API 变更
引入的 Redis 复制变更:
(1)在 `replconf` 命令中添加了新的复制能力:`capa rdb-channel-repl`。表示副本支持 RDB 通道复制。副本在连接到主节点时会发送该能力,与其他能力一起发送。
(2)如果副本需要全量同步,主节点会向副本的 `PSYNC` 请求回复 `+RDBCHANNELSYNC <client-id>`。
(3)当副本打开 RDB 通道连接时,作为 `replconf` 命令的一部分,它会发送 `rdb-channel 1` 以通知主节点这是 RDB 通道。同时,它还会在 `replconf` 命令中发送 `main-ch-client-id <client-id>`,以便主节点可以将这些通道关联起来。
副本状态图
具体实现细节请参考:https://github.com/redis/redis/pull/13732