Redis主从复制工作原理

TrumanWong
12/6/2024
TrumanWong

Redis主从复制有以下三个阶段:

建立连接

这一阶段的作用是主从节点建立连接,为数据同步做好准备,具体的步骤如下:

数据同步

主从节点之间的连接建立好以后便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化,具体执行的方式是:从节点向主节点发送psync命令,并且开始同步。数据同步阶段是主从复制的核心阶段,根据主从节点的当前状态可以分为全量复制部分复制

全量复制

当主从连接建立成功后初次复制或无法进行部分复制的时候,将主节点中的所有数据都发送给从节点,比部分复制耗时。

当启动一个从节点的时候,它会发送一个PSYNC命令给主节点,如果这时从节点重新连接主节点,那么主节点仅仅会复制给从节点部分缺少的数据;如果是从节点第一次连接主节点,就会触发一次全量重新同步ull resynchronization

开始全量重新同步时,主节点收到全量复制的命令后,执行bgsave命令,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令来保证数据的完整性。

主节点的bgsave命令执行完成后,将RDB文件发送给从节点。从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave命令时的数据库状态。

主节点将复制缓冲区中的所有写命令并发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态。所以这里会存在一个问题,如果缓冲区有修改的数据,那么从节点收到的数据就不是最新的数据

当从节点判断无法进行部分复制时,会向主节点发送全量复制的请求,或从节点发送部分复制的请求,当主节点判断无法进行部分复制时也会触发全量复制。

全量复制源码请参阅replication.c中的readSyncBulkPayload方法。

部分复制

从全量复制的过程可以看出,全量复制有如下几个非常重要的操作:

根据以上几点,我们可以到全量复制在主节点数据量较大时效率太低,所以从Redis 2.8开始就支持主从复制的断点续传,如果主从复制过程中网络连接断掉了,那么可以接着上次复制的地方继续复制下去,而不是从头开始复制,这样在很大程度上保证了主从复制的性能。

部分复制的实现主要依赖于三个重要的特征:

部分复制源码请参阅replication.c中的replicationResurrectCachedMaster方法。

命令传播

数据同步阶段完成后,主从节点进入命令传播阶段。在这一阶段,主节点将自己执行的写命令发送给从节点,从节点接收到命令并且执行,从而保证主从节点数据的一致性。

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:pingreplconf ack。心跳机制是指主从复制的超时判断,即每隔指定的时间主节点会向从节点发送ping命令,ping命令的目的主要是让从节点进行超时判断。心跳机制是在replicationCron函数中实现的,源码如下:

/* First, send PING according to ping_slave_period. */
    if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&
        listLength(server.slaves))
    {
        /* Note that we don't send the PING if the clients are paused during
         * a Redis Cluster manual failover: the PING we send will otherwise
         * alter the replication offsets of master and slave, and will no longer
         * match the one stored into 'mf_master_offset' state. */
        int manual_failover_in_progress =
            ((server.cluster_enabled &&
              clusterManualFailoverTimeLimit()) ||
            server.failover_end_time) &&
            isPausedActionsWithUpdate(PAUSE_ACTION_REPLICA);

        if (!manual_failover_in_progress) {
            ping_argv[0] = shared.ping;
            replicationFeedSlaves(server.slaves, -1,
                ping_argv, 1);
        }
    }

ping命令的作用如下:

在命令传播阶段,从节点会向主节点发送replconf ack命令,频率也是每秒1次。命令格式为:

replconf ack{offset}

其中offset是指从节点保存的复制偏移量。replconf ack命令的意义和作用如下:

延迟不一致

命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复,因此实际上主从节点之间很难保持实时的一致性。数据不一致的程度与主从节点之间的网络状况、主节点写命令的执行频率以及主节点中的repl-disable-tcp-nodelay配置有关,该配置有两个值:

一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时才会设置为yes,多数情况下使用默认设置值no