Redis主从复制原理

前言

在学习了Redis持久化之后,对Redis重启的数据恢复已经有一定了解,但是Redis持久化毕竟是单机的。为了避免单节点问题,要去学习Redis的集群知识,那么在了解集群之前,首先要知道多台节点之间的数据是如何同步的。这一章主要是对Redis的复制功能进行总结。

使用 info replication 可以检查当前Redis实例的主从功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
基于redis 4.0.12
redis-aliyun:0>info replication
"# Replication
role:master #当前节点的角色
connected_slaves:0 #当前连接的从节点数量
master_replid:3a7684e173291bc7766e37d600ce0b1108354879 #runid
master_replid2:0000000000000000000000000000000000000000 #上次的主节点runid
master_repl_offset:0 #数据偏移量
second_repl_offset:-1 #上次主节点的数据偏移量
repl_backlog_active:0 #复制积压缓冲区是否开启
repl_backlog_size:1048576 #复制积压缓冲区大小
repl_backlog_first_byte_offset:0 #复制积压缓冲区中偏移量的大小
repl_backlog_histlen:0
#当前复制积压缓冲区中的数据 master_repl_offset - repl_backlog_first_byte
"

理解Redis的主从架构首先理解两个概念:

  • 同步
    同步可以使从服务器与主服务器达到一个相同的状态。
  • 命令传播
    命令传播是指当主从服务器达到一个相同状态之后,如果主服务器又接收到了写命令,使主从服务器从不相同再重新达到相同的一个操作。

当决定一台Redis节点B 要做另一台Redis节点A的从节点的时候,可以在RedisB的控制台上执行

slaveof Ip port (A的IP与端口号)

同步

全量同步

slaveof命令主要是通过SYNC命令来完成的,以下是SYNC命令的过程:

  1. 从服务器向主服务器发送SYNC命令。
  2. 主服务器收到SYNC命令,并执行bgsave操作。在生成RDB文件的同时,将新的写操作加入一个缓冲区。
  3. 主服务器将生成好的RDB文件传送给从服务器,从服务器接收RDB文件,并将RDB文件载入到自己的内存中。此时从服务器中的内容与主服务器执行bgsave那一刻的内容相同。
  4. 主服务器将记录在缓冲区中的内容传输给从服务器,从服务器收到数据后进进行数据的更新,达到和主服务器相同的状态。
  5. 从服务器开始接受请求。
命令传播

当从服务器与主服务器同步完成后,如果主服务器又接收到了新的写命令,这个时候主从又到了一个不一致的状态。为了保证主从数据的一致性,在同步操作完成后,主服务器会将每个写操作都传给从服务器,以达到数据状态一致的目的。

Redis2.8之前复制的缺点

Redis2.8之前的复制过程存在很大的问题:

  • 如果同步过程完成,在命令传播阶段主从连接断开,那么从服务器重新连接到主服务器上之后会再次执行SYNC命令。
  • 如果从服务器在与主服务进行同步的过程中发生了网络中断,或者网络波动,这个时候会进行两次,甚至多次同步的操作。

上面有讲master在完成同步操作的过程中会执行bgsave命令,虽然bgsave是基于fork的,不会影响主服务器对外提供服务。但是频繁的bgsave操作也会造成性能的严重浪费:

  • 在fork的过程中会造成短时间的主进程阻塞。
  • 虽然使用了copy-on-write操作,但是当主进程的数据修改了,仍旧会造成主服务的内存严重浪费。
  • Redis频繁保存RDB文件,浪费磁盘IO,和CPU资源。
  • 主服务频繁给从服务器发送RDB文件,如果RDB文件过大,会浪费网络资源。

增量同步

为了解决上述问题,从Redis2.8之后加入了PSYNC命令。
PSYNC 的同步过程分为两种模式:

  • 完整同步
  • 部分同步
    完整同步与上面的全量同步一样,这里介绍的是新增的内容:部分重同步
    部分同步(增量同步)解决了主从断开连接之后,从服务器必须再进行一次全量同步的问题。

PSYNC命令的实现原理:

PSYNC中的三个重要组成部分
主服务器的偏移地址与从服务器的偏移地址

主从服务器分别会维护一个偏移地址,主服务每次向从服务器发送N自己的数据,主服务器的偏移地址会加上N。同理从服务器每接收N字节的数据,从服务器的偏移地址会加上N。

主服务的复制积压缓冲区

复制积压缓冲区是一个固定长度的先进先出的队列,默认大小为1MB。
在主服务进行命令传播的同时,也会将数据加到复制积压缓冲区中。
复制积压缓冲区不仅包含写的数据,同时还包含了该数据的数据偏移地址。

主服务器的运行ID

Redis中主从服务器均有自己的运行ID。
从服务在第一次进行同步操作的时候,主服务器会将自身的ID传给从服务器,从服务器进行保存。在从服务断线重连进行PSYNC命令的时候不仅会发送自身的偏移量,还会发送主服务器的ID。

  • 如果该ID与主服务器ID不同,说明主服务器发生了重启,这时候要进行全量同步。
  • 如果该ID与主服务器相同,那么进行增量同步。

数据解压缓冲区的大小可以设置。公式为: 2 second(断线之后平均重连耗费的时间) 每秒会写入的数据(最终根据Redis协议保存的文件的大小。)

PSYNC命令调用方式
  • 从服务在第一次调用slaveof 命令的时候 发送 PSYNC ? -1 代表全量同步。
  • 从服务器断线重连后,发送 PSYNC runid offset,进行增量同步。
PSYNC调用过程
  1. 从服务器向主服务发送slaveof命令
  2. 从服务器自己判断是否是第一次进行同步(以前从未发送过同步命令,或者执行过 slaveof no one命令)。
  3. 如过是第一次调用那么调用PSYNC [?] [ -1] 进行全量同步,如果是断线重连那么发送 PSYNC [runid] [offset]进行增量同步。
  4. 主服务器判断ID是否相同,如果不同,进行全量同步,如果相同那么继续。
  5. 查看offset+1 是否存在于复制积压缓冲区中,如果存在那么进行增量同步,如果不存在,则进行全量同步。

同步的过程

  1. 在从服务器执行slaveof命令后,会尝试与主服务建立连接。
  2. 在连接建立成功后,从服务器会Ping一次主服务器,以检查连接是否可用。如果连接异常,那么从服务器会释放该连接,并尝试重新建立连接。
  3. 如果从服务器设置了masterauth选项,那么在从服务器将会发送一条AUTH命令,尝试进行身份验证。 只有主服务器与从服务器都不设置密码校验,或者主服务与从服务器都设置了密码校验并且匹配成功,才会通过校验。否则均为失败,不会继续进行同步操作。
  4. 从服务向主服务器发送自己的监听端口,主服务器收到后进行保存。
  5. 执行同步操作。

心跳检测

在命令传播阶段,从服务器会以每秒一次的频率检测自身与master是否保持良好的连接情况。
通过心跳检测可以完成以下功能:

  • 检查主从之间的连接是否健康。
  • 作为实现min-slaves的参数
    min-slaves的意思是如果主服务器的可用从节点小于某个值且延迟大于多久之后,将拒绝写操作。
  • 检测命令丢失,实现数据补发的操作。
    心跳检测时候,从服务器会向主服务器发送自身的偏移地址,如果主服务器发现自身的偏移地址与从服务器的偏移地址不同,将会主动向从服务器发送数据。

Redis4.0中改进后的增量同步

虽然Redis2.8之后加入的部分同步解决了之前复制过程中的部分问题,但是它自身仍有一定的缺陷:

如果Redis master因为某些原因需要重启,这个时候它自身的id与复制偏移量均会丢失。或者Redis的master发生了故障转移(加入哨兵机制后会自动的选举leader,以实现主节点高可用),这个时候因为runid发生了变化,Redis会发生全量同步。

在Redis4.0之后加入了PSYNC2就是为了解决这些问题。

原理

在Redis4.0之后,主从服务器中不仅有master_repid(runid),又新增了一个master_repid2

  • master_repid2 主要用于存储上次主实例的replid,初始值为0.

为了解决PSYNC存在的问题,PSYNC2主要做了以下操作:

  • redis4.0中保存两组 runid 和 offset
    在主服务器中第一组runid以及offset与PSYNC中的意思相同分别为自身的运行时id以及复制积压缓冲区的数据偏移量。但是从节点的runid不再保存自己的运行时id,而是保存当前正在同步的master节点的runid.
    第二组rundid以及offset保存上次主服务器的runid以及偏移地址。
  • 从服务器默认开启复制积压缓冲区的功能。当master发生故障,slave被选为新的master后,其它落后于该节点的slave节点可以进行数据同步。

过程:

  1. 在从节点意外关闭之前会调用rdbSaveInfoAuxFields函数,该函数会把当前从节点的runId以及offset与内存中的数据一起存储到RDB文件中。
  2. 当从节点重启后,会加载RDB中的数据到内存。同时将之前保存的runid以及offset传送给主服务器。这里要注意的是,如果从服务器开启了AOF持久化,那么因为重启过程中AOF的加载是要优于RDB,所以这个时候重启后的从节点会重新进行全量复制。
  3. 主节点收到从节点的部分复制命令后,将自身的runid1与传送的runid,或者将自身runid2与传送的runid比较(在与runid2比较的时候,从节点的复制偏移量不能快于master的offset2)。只要有一个条件匹配那么就会继续匹配从节点的数据偏移量是否还在复制积压缓冲区,如果都符合那么会进行部分重同步,如果不匹配那么依旧会进行全量同步。

如果slave转为master这个时候slave的runid2将会保存之前的master的runid,offset2+1将会保存之前的offset.

关于PSYNC2可以参考这篇博客:

https://www.cnblogs.com/wdliu/p/9407179.html

无磁盘化复制

如上面所介绍,一次全量复制需要Redis主节点先在磁盘中存入一份RDB文件,之后再将RDB文件传输给从节点。如果主节点的IO性能不高,那么很影响集群的复制效率。从Redis2.8之后增加无磁盘化复制的功能。
通过在redis.conf中设置:

repl-diskless-sync yes

开启无磁盘化复制。另外在配置文件中有个repl-diskless-sync-delay参数

repl-diskless-sync-delay 5

该配置的意义是: 为了等待更多的从服务器与主服务器建立连接。因为没有生成RDB文件,那么一旦主服务器与从服务器的数据传输开始,新连接上来的从服务器将等待下一次RDB文件的生成。这个参数默认为5秒。

经测试,无磁盘化复制并不影响Redis本身的RDB持久化操作。

注意

如果主服务器本身设置了不生成RDB文件,那么就要将主服务器的自动重启关闭。否则的话,如果主服务器重启时间很短,连哨兵都没发觉的话,那么主服务器会将空数据同步给从服务器造成数据丢失。


下面将会了解Redis的集群架构,哨兵机制。