redis 无盘复制(redis repl-diskless-sync)
无盘复制的需求背景
redis在启动后自动生成一个唯一的标识符run_id,这个run_id在持久化的时候存在于RDB文件中(如果有启用RDB或者做了RBD备份操作),但是并不存在与AOF文件中(包括混合是持久化文件),如果下次重启从RDB加载数据,那么这个run_id可以保存。如果同时启用了RDB和AOF持久化,redis重启时会优先加载AOF文件,如果从AOF加载,那么这个run_id会重新生成。
redis复制环境下(不管是简单的主从复制,Sentinel,Cluster)主从节点之间依赖run_id做身份标识,当redis重启后,如果不是从RDB中加载的,其身份标识run_id会发生变化,当前实例相当于一个全新的节点,此时会进行一个全量的同步操作(比如从节点重启后,run_id发生了变化,从节点的身份会变为一个“全新的节点”,会向主节点做一次全量同步的请求),以从节点重启后run_id重新生成导致的全量同步步骤大概如下:
----》从节点请求全量同步
--------》主节点生成RDB文件到磁盘(主节点磁盘写操作)
----------》主节点发送RDB文件到从节点(主节点磁盘读操作)
------------》从节点接收主节点发送的RDB文件到本地磁盘(从节点磁盘写操作)
--------------》从节点接从本地接收的RDB文件到加载数据到磁盘(从节点磁盘读操作)
如果是主节点重启,从AOF中加载持久化的数据,那么会从节点会做类似的全量同步操作
可见全量同步过程中,会在主节点上经历一次磁盘写操作和一次磁盘读操作,从节点上经历一次磁盘写操作和磁盘读操作,如果磁盘速度跟不上,可能会拉长全量同步的时长,因此无盘同步的需求就出现了(假设节点间网络没有瓶颈)。
相关参数
影响主节点的参数
repl-diskless-sync
该参数默认值为no,也即从节点请求全量复制的时候,主节点上不开启无盘复制,先通过bgsave做持久化,然后传输RDB文件到从节点,
全量同步的可以通过两种方式进行:
- 基于磁盘的复制:Redis主节点会创建一个新的进程,在磁盘上写入RDB文件。之后,父进程会逐步将文件传输给副本。
- 无磁盘复制:Redis主节点创建的新进程会直接将RDB文件写入副本的套接字,完全不涉及磁盘操作。
在使用基于磁盘的复制时,当RDB文件正在生成时,其他的副本可以排队,并能在当前子进程完成RDB文件生成后立刻使用这个文件。
在磁盘速度慢而网络速度快(带宽大)的情况下,无磁盘复制表现得更好。这是因为无磁盘复制避免了磁盘I/O操作的瓶颈,可以更快地通过网络直接将数据发送给副本。而基于磁盘的复制方式在传输前需要将数据写入磁盘,这在磁盘性能不佳的情况下可能会成为性能瓶颈。因此,在网络速度快而磁盘速度慢的环境中,选择无磁盘复制通常是一个更好的选择。
repl-diskless-sync-delay
默认值为5,单位为秒,该参数只有开启了repl-diskless-sync 为yes的时候才会生效
在使用无磁盘复制时,一旦传输开始,新到达的副本会被排队,直到当前传输结束,新的传输才会开始。
当使用无磁盘复制时,主节点会等待一个时间段repl-diskless-sync-delay(以秒为单位)再开始传输,这样做的目的是希望多个副本能够在这段时间内到达,从而使传输过程可以并行化。
repl-diskless-sync-max-replicas
该参数默认值为0
当使用延迟启用无磁盘复制时,如果连接的副本达到预期的最大数量,则可以在达到最大延迟之前启动复制。默认值为0表示没有定义最大值,Redis将等待完整的repl-diskless-sync-delay。
影响从节点的参数
repl-diskless-load
该参数默认为disabled。
副本可以直接从socket中加载它从复制链接读取的RDB,直接加载到内存或者暂存在本地磁盘,完全接受主节点的RDB文件之后再加载到内存。
副本可以直接从socket中加载它从复制链接读取的RDB,直接加载到内存或者暂存在本地磁盘,完全接受主节点的RDB文件之后再加载到内存。
在许多情况下,磁盘比网络慢,存储RDB到磁盘和加载RDB文件到内存,可能会增加复制时间(甚至会增加主服务器的Copy on Write内存和副本缓冲区)。但是,当直接从套接字解析RDB文件时,为了避免数据丢失,只有在新数据集完全加载到内存中时才会刷新当前数据集,这将导致更高的内存使用。出于这个原因,我们有以下选项:
- "disabled" :(从节点)不使用无磁盘加载(首先将rdb文件存储到磁盘),特别说明,这是一个双重否定句,等于是肯定句,字面意思很别扭,意思是完整地将RDB写入磁盘文件之后,再从磁盘加载到内存。
- "swapdb" :在直接从套接字解析数据时,将当前数据库内容保存在RAM中。这种模式下的副本可以在复制过程中继续服务当前数据集,除非它们不能识别出主数据集具有来自相同复制历史的数据集。注意,这需要足够的内存(理论上需要双倍的内存,一份是从主节点接收的RDB文件,一份是从RDB文件转换为当前实例的内存),如果没有,将会存在OOM的风险。
- "on-empty-db" :只在当前数据集为空时使用无磁盘加载。这样更安全,避免了在复制过程中同时加载旧数据集和新数据集。
无盘复制过程
一个无盘复制的日志大概如下
从节点的日志
33300:S 06 Apr 2024 19:29:32.276 * Connecting to MASTER ***.***.***.***:*** #从节点连接至主节点 33300:S 06 Apr 2024 19:29:32.276 * MASTER <-> REPLICA sync started 33300:S 06 Apr 2024 19:29:32.276 * Non blocking connect for SYNC fired the event. 33300:S 06 Apr 2024 19:29:32.276 * Master replied to PING, replication can continue... 33300:S 06 Apr 2024 19:29:32.277 * Partial resynchronization not possible (no cached master) #从节点重启后,run_id发生变化,作为一个新的从节点,需要做全量同步 33300:S 06 Apr 2024 19:29:37.953 * Full resync from master: f2ec97e11ec65d3e9ecdfd822b93905a9fac37a3:911741 33300:S 06 Apr 2024 19:29:37.958 * MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF to parser #从节点从socket中接收主节点的全量RDB数据 33300:S 06 Apr 2024 19:29:37.958 * MASTER <-> REPLICA sync: Loading DB in memory 33300:S 06 Apr 2024 19:29:37.958 * Loading RDB produced by version 7.0.11 33300:S 06 Apr 2024 19:29:37.958 * RDB age 0 seconds 33300:S 06 Apr 2024 19:29:37.958 * RDB memory usage when created 62.40 Mb 33300:S 06 Apr 2024 19:30:25.667 * Done loading RDB, keys loaded: 191498, keys expired: 0. 33300:S 06 Apr 2024 19:30:25.667 * MASTER <-> REPLICA sync: Swapping active DB with loaded DB 33300:S 06 Apr 2024 19:30:25.667 * MASTER <-> REPLICA sync: Discarding old DB in background 33300:S 06 Apr 2024 19:30:25.667 * MASTER <-> REPLICA sync: Finished with success #从节点加载全量数据
主节点的日志
32489:M 06 Apr 2024 19:29:32.277 * Replica ***.***.***.***:*** asks for synchronization #从节点请求主节点做全量复制
32489:M 06 Apr 2024 19:29:32.277 * Full resync requested by replica ***.***.***.***:***
32489:M 06 Apr 2024 19:29:32.277 * Delay next BGSAVE for diskless SYNC #主节点由于diskless复制,并没有做bgsave持久化操作
32489:M 06 Apr 2024 19:29:37.952 * Starting BGSAVE for SYNC with target: replicas sockets #主节点以socket网络传送方式传递全量数据
32489:M 06 Apr 2024 19:29:37.954 * Background RDB transfer started by pid 33306
33306:C 06 Apr 2024 19:30:25.186 * Fork CoW for RDB: current 1 MB, peak 1 MB, average 1 MB
32489:M 06 Apr 2024 19:30:25.186 # Diskless rdb transfer, done reading from pipe, 1 replicas still up.
32489:M 06 Apr 2024 19:30:25.276 * Background RDB transfer terminated with success
32489:M 06 Apr 2024 19:30:25.276 * Streamed RDB transfer with replica ***.***.***.***:*** succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming
32489:M 06 Apr 2024 19:30:25.276 * Synchronization with replica ***.***.***.***:*** succeeded
当从节点以"swapdb"在加载数据的过程中(一遍接收主节点socket数据一边载入当前实例中),当前实例不可访问
对于Redis集群模式,不管是sentinel还是cluster,主从节点的身份都是相对的,都可以在故障发生时做主从切换,因此如果想要实现无盘复制,这几个参数需要再主从节点同时配置。