Redis高可用初步之主从复制原理

(一)什么是主从复制

主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master),后者称为从节点(slave)。数据的复制是单向的,只能由主节点到从节点。主节点负责写操作,从节点负责读操作。主从复制把数据复制多个副本部署到其他节点上,从而实现redis的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。

redis 集群中从节点重启 redis从节点作用_数据库

简单来说,主从复制最主要的作用就是保证数据的可靠性,在服务器由于断电而宕机或者硬盘损坏的情况下,主从复制的多节点数据备份可以非常方便快捷的进行数据恢复。除了保证可靠性之外,主从复制还有其他的作用:

1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4.高可用基石:主从复制是哨兵和集群能够实施的基础,因此主从复制是Redis高可用的基础。

(二)主从复制的三个阶段

主从复制完整的工作流程分为以下三个阶段,每一段都有自己的内部工作流程,那么我们会对这三个过程进行研究。

  1. 建立连接过程:slave跟master连接的过程
  2. 数据同步过程:master给slave同步数据的过程
  3. 命令传播过程:master给slave反复同步数据

redis 集群中从节点重启 redis从节点作用_分布式_02

1. 建立连接过程

建立连接是主从复制的第一步。简单说一下建立连接阶段的工作流程:

  1. slave设置master的地址和端口,保存master的信息
  2. 建立socket连接
  3. 持续发送ping命令
  4. 身份验证
  5. 发送slave端口信息给master

很显然,在建立连接的过程中,从节点会保存master的地址和端口、主节点master保存从节点slave的端口。下面是流程图:

redis 集群中从节点重启 redis从节点作用_数据库_03

2. 数据同步过程

当从节点第一次连接主节点时,先会执行一次全量复制,这次的全量复制是无法避免的,虽然全量复制是非常消耗资源的。全量复制执行完成后,主节点就会发送复制缓冲区的数据给从节点,然后从节点就会执行bgrewriteaof命令恢复数据,这也就是部分复制。下面是流程图:

redis 集群中从节点重启 redis从节点作用_redis 集群中从节点重启_04

3. 命令传播阶段

当master数据库被修改,主从服务器的数据不一致后,此时就会让主从数据同步到一致,这个过程称之为命令传播。master会将接收到的数据变更命令发送给slave,slave接收命令后执行命令,让主从数据达到一致。

(三)全量复制与部分复制

下面我们来仔细的讲一下全量复制和部分复制:

redis 集群中从节点重启 redis从节点作用_分布式_05

  1. 从节点发送指令psync ? 1 psync runid offset找对应的runid的节点索取数据,但是这里有一个问题,当从节点第一次连接的时候根本就不知道主节点的runid 和offset 。所以第一次发送的指令是psync ?1,意思就是主节点的数据需要全部同步过来。
  2. 主节点开始执行bgsave生成RDB文件,记录当前的复制偏移量offset。
  3. 主节点把自己的runid和offset通过+FULLRESYNC runid offset指令发送给从节点,然后通过socket发送RDB文件也发送给从节点。在这个阶段内可能主节点会收到客户端的指令,offset发生了变化。
  4. 从节点接收到主节点的runid和offset并将其保存下来,然后清空数据库当前所有数据,通过socket接收RDB文件,开始恢复RDB数据(全量复制)。
  5. 在全量复制后,从节点已经获取到了主节点的runid和offset,开始发送指令psync runid offset。
  6. 主节点接收指令,判断runid是否匹配,同时判断offset是否在复制缓冲区中。
  7. 主节点判断runid和offset,若有一个不满足,就会在返回到步骤2继续执行全量复制。这里runid不匹配的原因可能是从节点意外重启,offset(偏移量)不匹配的原因是复制缓冲区溢出了。如果runid或offset校验通过,从节点的offset和主节点的offset相同时则忽略,如果从节点的offset与主节点的offset不相同,则主节点会发送+CONTINUE offset(这个offset为主节点的)命令,通过socket发送复制缓冲区中以从节点offset开始到主节点offset结束的操作(实际上就是从节点与主节点相比少执行的操作)。
  8. 从节点收到主节点发送的offset并且通过socket接收到信息后,执行bgrewriteaof,恢复数据。

很显然,我们发现部分复制要比全量复制复杂很多,所以我们来看一下部分复制里面的一些细节:

1. Runid

上面多次提到了这个runid,它实际上runid是redis在启动时会自动生成的一个随机的id(这里需要注意的是每次启动的id都会不一样),是由40个随机的十六进制字符串组成,用来唯一识别一个redis节点。

在主从复制初次启动时,master会把自己的runid发送给slave,slave会保存master的这个id。当slave断线重连时,slave把这个id发送给master,如果slave保存的runid与master现在的runid相同,master会尝试使用部分复制(能否复制成功还有一个因素就是offset,上面讲过)。如果slave保存的runid与master现在的runid不同,则会直接进行全量复制。

2. 复制缓冲区

复制缓冲区是一个先进先出的队列,用来存储master收集数据的命令记录。复制缓冲区的默认存储空间是1M。可以在配置文件修改repl-backlog-size 1mb来控制缓冲区大小。

redis 集群中从节点重启 redis从节点作用_redis_06

实际上,复制缓冲区就是存储的aof持久化的数据(aof持久化记录的是客户端发出的命令),并且以字节分开,并且每个字节都有自己的偏移量,这个偏移量也就是我们前面提到的复制偏移量(offset)。

前面我们提到过,如果复制缓冲区空间不足可能会导致全量复制,那是什么原因呢?在命令传播阶段,主节点会把收集的命令存储到复制缓冲区中,然后在发送给从节点。就是这里出现了问题,当主节点收到客户端的命令数量在一瞬间特别大的时候,超出了复制缓冲区的内存,就会有一部分数据会被挤出去,从而导致主节点和从节点的命令不一致,从而进行全量复制。如果这个缓冲区大小设置不合理那么很大可能会造成死循环,从节点就会一直全量复制,清空数据,全量复制。

3. 复制偏移量

主节点复制偏移量是给从节点发送一次记录一次,从节点是接收一次记录一次。用于同步信息,对比主节点和从节点的差异,当slave断联时恢复数据使用。这个值也就是来自己于复制缓冲积压区里边的那个偏移量。

redis 集群中从节点重启 redis从节点作用_分布式_07

(四)心跳机制

我们可以想一个问题,在主从复制的过程中,我们一定是需要知道主节点和从节点是否在线并且正常运转,如果遇到某个节点延迟高或者无响应的情况,应当采取相应措施,比如停止主从复制等,那么各个节点如何知道其他节点是否在线?

在命令传播阶段是,主节点与从节点之间一直都需要进行信息互换,这种信息交换使用心跳机制进行维护,实现主节点和从节点连接保持在线情况的互相感知。下面我们来看看master节点的心跳和slave心跳分别起什么作用:

1. master心跳

指令:ping
默认10秒进行一次,是由参数repl-ping-slave-period决定
主要负责判断从节点是否在线
可以使用info replication来查看从节点租后一次连接时间的间隔,lag为0或者为1就是正常状态。

2. slave心跳

指令:replconf ack {offset}
每秒执行一次
主要做的事情是给主节点发送自己的复制偏移量,从主节点获取到最新的数据变更命令,还负责判断主节点是否在线。

注意:主节点为保障数据稳定性,当从节点挂掉的数量超过一个设定值或者延迟过高时,将会拒绝所有信息同步。这里有俩个参数可以进行配置调整:

  • min-slaves-to-write 2
  • min-slaves-max-lag 8

这两个参数表示从节点的数量就剩余2个,或者从节点的延迟大于8秒时,主节点就会强制关闭maste功能,停止数据同步。那么主节点是如何知道从节点挂掉的数量和延迟时间呢?上面讲过,在心跳机制里边slave会每隔一秒发送perlconf ack这个指令,这个指令可携带偏移量,也可以携带从节点的延迟时间和从节点的数量。

2020年9月15日