什么是主从复制

在Redis中,用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项,让一个服务器去复制另一个服务器,我们称呼被复制的服务器为主服务器master,而对主服务器进行复制的服务器被称为slave。

一个master可以有多个slave,即一主多从;而slave也可以接受其他slave的连接,形成“主从链”层叠状结构(cascading-like structure),自 Redis 4.0 起,所有的sub-slave也会从master收到完全一样的复制流。如下图

image-20210507135720478

主从复制的好处:

  • 数据冗余,实现数据的热备份
  • 故障恢复,避免单点故障带来的服务不可用
  • 读写分离,负载均衡。主节点负载读写,从节点负责读,提高服务器并发量
  • 高可用基础,是哨兵机制和集群实现的基础

主从复制的实现原理

Redis的主从复制过程大体上分3个阶段:建立连接数据同步命令传播

建立连接阶段

这个阶段主要是从服务器发出slaveof命令之后,与主服务器如何建立连接,为数据同步做准备的过程。

1)在slaveof命令执行之后,从服务器根据设置的master的ip地址和端口,创建连向主服务器的socket套接字连接,连接成功后,从服务器会为这个套接字关联一个专门的处理器,用于处理后续的复制工作

2)建立连接之后,从服务器会向主服务器发送ping命令,确认主服务器是否可用,以及当前是否可用接受处理命令。如果收到主服务器的pong回复说明是可用的,否则有可能是网络超时或主服务器阻塞,从服务器会断开连接发起重连

3)身份验证。如果主服务器设置了requirepass选项,那么从服务器必须配置masterauth选项,且保证密码一致才能通过验证

4)身份验证完成之后,从服务器会发送自己的监听端口,主服务器会保存下来

数据同步阶段

在主从服务器建立连接确认各自身份之后,就开始数据同步,从服务器向主服务器发送PSYNC命令,执行同步操作,并把自己的数据库状态更新至主服务器的数据库状态

Redis的主从同步分为:完整重同步(full resynchronization)部分重同步(partial resynchronization)

  • 发生完整重同步一般发生在slave连接上master第一次执行复制的时候。
  • 部分重同步则是用于处理断线后重新连接的复制。但是如果master的复制积压缓存区没有足够的命令记录,或者slave传的runid不对,就会进行完整重同步,即slave会获得一个完整的数据集副本。

PSYNC命令执行完整重同步和部分重同步的流程图:

image-20210507183835600

完整重同步

有两种情况下是完整重同步,一是slave连接上master第一次复制的时候;二是如果当主从断线,重新连接复制的时候有可能是完整重同步。具体步骤如下:

  • 从服务器连接主服务器,发送PSYNC命令
  • 主服务器接收到PSYNC命名后,开始执行bgsave命令生成RDB文件并使用缓冲区记录此后执行的所有写命令
  • 主服务器bgsave执行完后,向所有从服务器发送快照文件,并再发送期间继续记录被执行的写命令
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令
  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令

image-20210507200034827

部分重同步

部分重同步是用于处理断线后重复制的情况,先介绍几个用于部分重同步的部分:

  • 复制偏移量 offset。主服务器和从服务器各自维护一个复制偏移量,记录传输的字节数。当主节点向从节点发送N个字节数据时,主节点的offset增加N,从节点收到主节点传来的N个字节数据时,从节点的offset增加N。
  • 服务器运行id run ID,Redis实例在启动时,随机生成一个长度40的唯一字符串来标识当前节点。
  • 复制积压缓冲区 replication backlog buffer。是一个固定长度的FIFO队列,大小由配置参数repl-backlog-size指定,默认大小1MB。需要注意的是该缓冲区由master维护并且有且只有一个,所有slave共享此缓冲区,其作用在于备份最近主库发送给从库的数据。

当slave连接到master,会执行PSYNC <runid> <offset>发送记录旧的master的runid和偏移量offset,这样master能够只发送slave所缺的增量部分。但是如果master的复制积压缓存区没有足够的命令记录,或者slave传的runid不对,就会进行完整重同步,即slave会获得一个完整的数据集副本。

image-20210507202456427

命令传播阶段

当完成数据同步之后,主从服务器的数据暂时达到一致状态,当主服务器执行了客户端的写命令之后,主从的数据便不再一致。为了能够使主从服务器的数据保持一致性,主服务器会对从服务器执行命令传播操作,即每执行一个写命令就会向从服务器发送同样的写命令

另外在命令传播阶段,从服务器会默认以每秒一次的频率向主服务器发送心跳检测

REPLCONF ACK <replication_offset>

其中replication_offset是当前从服务器的复制偏移量,该命令的作用有三个:

  • 检测主从服务器的网络连接状态
  • 辅助实现min-slaves选项
  • 检测命令丢失

断点续传

主从节点互相都会发送heartbeat信息。

master默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat,master和slave都会维护一个offset:

  • master在自身基础上累加offset,slave亦是。
  • slave每秒都会上报自己的offset给master,同时master保存每个slave的offset。

主从复制过程,若网络连接中断,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。

主从复制导致的数据丢失

未开启持久化

在使用Redis复制功能时的设置中,强烈建议在master和在slave中启用持久化。当不可能启用时,例如由于非常慢的磁盘性能而导致的延迟问题,应该配置实例来避免重置后自动重启

关于关闭持久化时,主从复制存在安全性问题,比如以下场景:

  1. 我们设置节点 A 为 master 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据。
  2. 节点 A 崩溃,但是他有一些自动重启的系统可以重启进程。但是由于持久化被关闭了,节点重启后其数据集合为空。
  3. 节点 B 和 节点 C 会从节点 A 复制数据,但是节点 A 的数据集是空的,因此复制的结果是它们会销毁自身之前的数据副本。

当 Redis Sentinel 被用于高可用并且 master 关闭持久化,这时如果允许自动重启进程也是很危险的。例如, master 可以重启的足够快以致于 Sentinel 没有探测到故障,因此上述的故障模式也会发生。

任何时候数据安全性都是很重要的,所以如果 master 使用复制功能的同时未配置持久化,那么自动重启进程这项应该被禁用。

异步复制

异步复制导致的数据丢失:因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了。

主备切换导致脑裂

主备切换后,某个slave被切换成了master,但是有些client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据。导致数据丢失。

⭐️ 对于异步复制和脑裂,可以配置min-slaves-to-write 1min-slaves-max-lag 10两个参数来防止主服务器在不安全的情况下执行写命令,即在从服务器的数量小于1或者从服务器的延迟都大于10s的时,主服务器将拒绝执行写命令。

min-slaves-to-write 1
min-slaves-max-lag 10

总结 🤔

Redis的主从复制有一主多从、主从链两种配置方式,配置了主从复制可以实现数据的热备份、避免单点故障、读写分析提高并发量,是哨兵和集群机制的基础。

Redis的主从复制大体可以分为建立连接、数据同步、命令传播三个阶段。建立连接阶段的话首先是从服务器slave发出slaveof命令,与主服务器master建立TCP连接。建立连接后slave还会发送ping命令来确认主服务器是否可用,如果主服务器回复pong则说明是可用的。另外如果设置了requirepass选项的话,从服务器还需要配置masterauth选项且保证密码一致才能通过验证。

接下来是数据同步阶段,如果是当前slave是第一次执行复制的话,会使用完整重同步。从服务器会发送 psync ?-1同步命令,如果master返回 +fullresync <runid> <offset>表示将与slave进行完整重同步。此时master会把该客户端添加进slave列表,并启动一个子进程来执行bgsave命令来生成RDB文件并向所有从服务器发送RDB快照文件,此外还会使用缓冲区记录执行命令和发送快照期间所有的写命令。从服务器加载完RDB后,master会给从服务器发送缓存区保存的写命令进行执行。

如果是断线后重连的场景,则会使用部分重同步。这时候salve发送的同步命令就会带上服务器的runid和复制偏移量offerset,master如果回复 +continue则表示将与slave执行部分重同步。然后master会根据复制偏移量发送复制积压缓冲区中的数据给slave执行。但是如果master的复制积压缓存区没有足够的命令记录,或者slave传的runid不对,还是会进行完整重同步,即slave会获得一个完整的数据集副本。

最后是命令传播阶段,即每执行一个写命令就会向从服务器发送同样的写命令,目的是为了保证主从服务器的数据一致性。

另外主从复制一定要开启持久化,不然如果master宕机并重启的话,就会造成主从服务器数据的丢失。此外异步复制和主备切换造成的脑裂也会导致数据丢失,这时候可以配置从服务器的最少数量和从服务器的最大延迟两个参数来防止主服务器在不安全的情况下执行写命令。

参考: