Redis高可用——主从复制原理

498 阅读12分钟

前言

Redis 的高可用方案包括:持久化、主从复制、哨兵和集群。其中持久化侧重解决的是 Redis 数据的单机备份问题;而主从复制则侧重解决数据的多机热备,此外,主从复制还可以实现负载均衡和故障恢复。

本文主要介绍 Redis主从复制,包括如何使用主从复制、主从复制相关的配置、主从复制的原理等。

主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

主从复制的作用:

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

配置主从

Redis主从复制的开启,完全是在从节点发起,不需要主节点做任何事情。

配置当前主机为指定主机的 slave节点,有三种方式:

  • 配置文件
  • 启动命令
  • 客户端命令

上述三种方式都可以开启主从复制,命令:slaveof <masterip> <masterport>

下面为客户端命令实例:

127.0.0.1:6379> SLAVEOF 192.168.96.148 6379
OK

实现原理

1、保存主节点信息

从执行主从配置的命令时,从节点服务器内部维护了两个字段:masteripmasterport,用于存储主节点的ip和port

slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回 ok

2、建立 socket 连接

从节点以 1次/s 的频率调用复制定时函数 replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建 socket 连接,如果连接成功,则:

  • 从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等
  • 主节点:接收到从节点的 socket 连接后(即 accept 之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行

3、发送 Ping 命令

从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查 socket 连接是否可用,以及主节点当前是否能够处理请求。

从节点发送 ping 命令后,可能出现3中情况:

  • 返回 pong :说明 socket 连接正常,且主节点当前可以处理请求,复制过程继续
  • 超时:一定时间后从节点仍未收到主节点的回复,说明 socket 连接不可用,则从节点断开 socket 连接,并重连
  • 返回 pong 以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连

4、身份验证

如果从节点中设置了 masterauth 选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送 auth 命令进行的,auth 命令的参数即为配置文件中的masterauth 的值。

如果主节点设置密码的状态,与从节点 masterauth 的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开 socket 连接,并重连。

5、发送从节点端口信息

身份验证之后,从节点会向主节点发送其监听的端口号,主节点将该信息保存到该从节点对应的客户端的 slave_listening_port字段中;该端口信息除了在主节点中执行 info Replication 时显示以外,没有其他作用。

复制阶段

Redis的复制又分为:数据同步和命令传播两个操作。

数据同步阶段

主从节点之间的连接建立之后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。具体执行的方式是:从节点向主节点发送 psync 命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态。

数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制。

在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求,才能完成复制。

命令传播阶段

当完成了数据同步阶段,主从服务器就会进入命令传播阶段,这时主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接收并执行主服务器发来的写命令,就可以保证主从服务器一直保持一致了。

复制类型

全量复制

全量复制用于初次复制或者其他无法进行部分复制时的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。

Redis通过 psync 命令进行全量复制的过程如下:

  • 从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行部分复制
  • 主节点收到全量复制命令后,执行 bgsave ,在后台生成 RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令
  • 主节点的bgsave命令完成后,将 RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的 RDB 文件,将数据库状态更新至主节点执行 bgsave时的数据库状态
  • 主节点将前述复制缓冲区的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态
  • 如果从节点开启了 AOF ,则会触发 bgrewriteaof 的执行,从而保证 AOF 文件更新至主节点的最新状态

部分复制

用于网络中断等情况后的复制,只将中断区间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整的保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

由于全量复制在主节点数据量较大时效率低,因此 Redis 2.8开始提供部分复制,用于处理网络中断时的数据同步。

复制偏移量

主节点和从节点分别维护一个复制偏移量,代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。

offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。例如,如果主节点的offset是1000,而从节点的offset是500,那么部分复制就需要将offset为501-1000的数据传递给从节点。而offset为501-1000的数据存储的位置,就是下面要介绍的复制积压缓冲区。

复制积压缓冲区

复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。

在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。

由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。

从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:

  • 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制
  • 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制

服务器运行ID

每个 Redis 节点,在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid 用来唯一识别一个 Redis 节点,通过 info server 命令,可以查看节点的 runid

root@32b70cb74b21:/data# redis-cli info server|grep run_id
run_id:289b0f664d2b04e4db54176be6e281b0f34faddc

主从节点初次复制时,主节点将自己的 runid 发送给从节点,从节点将这个 runid 保存起来;当断线重连时,从节点会将这个 runid 发送给主节点;主节点根据 runid 判断能否进行部分复制:

  • 如果从节点保存的runid与主节点现在的 runid 相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

心跳机制

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

REPLCONF ACK <replication_offset>

其中 <replication_offset> 是从服务器当前的复制偏移量,发送 REPLCONF ACK 对主从服务器有三个作用:

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

检测主从服务器的网络连接状态

该命令会被主节点用于复制超时的判断。此外,在主节点中使用 info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1,如果超过1s的话,那么说明主从服务器之间的连接发生了故障。

辅助实现 min-slaves 选项

Redis主节点中使用min-slaves-to-writemin-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-writemin-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值

检测命令丢失

从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。注意, offset 和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的

小结

本文主要对 Redis高可用方案之一:主从机制的原理进行介绍,如对 Redis感兴趣可关注继续关注本专栏。