Redis主从复制的几种形式和原理详解

主从复制概述

       主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(salve)。数据的复制是单向的,只能从主节点到从结点。        默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从结点(或没有从结点),但是一个从结点只能有一个主节点。

主从复制的作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
  5. 高可用的基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

主从复制启用

前面提到过,默认情况下每台Redis服务器都是主节点(master),而且如果没有配置的话,是没有从服务器的。(可以使用info replication命令查看一个Redis服务器的复制有关的信息)

有三种方式可以开启主从:

  • 配置文件:在从服务器的配置文件中加入slaveof <主节点ip> <主节点port>
  • 启动命令:redis-server启动命令后面加入--slaveof <主节点ip> <主节点port>
  • 客户端命令:Redis服务器启动后,直接通过客户端执行命令slaveof <主节点ip> <主节点port>,返回OK后该Redis实例就成为了从节点。

Redis常用的主从拓扑

1主N从

所谓的1主N从指的是一个主Redis服务器(master)可以有一个或多个从Redis服务器(salve),这种拓扑关系的特点是:

  • 只有一个主节点,有一个或多个从结点;
  • 主节点可读可写,从结点只能从主节点读数据,不能自己写数据;
  • 当主服务节点宕机后(无论各种原因,反正主节点不能正常运行了),从服务节点不会自动变成主节点,而是保持自己从结点的身份继续运行(而且他从主节点复制的数据不会丢失,可一继续对外提供服务),直到主节点恢复后这些从结点又可一继续从主节点读数据;
  • 当一以从服务器结点“挂掉”以后,再次重启后,他与先前的主节点没有任何关系了(在没有在配置文件中配置的前提下),除非在配置文件中配置过或者再次使用命令slaveof <主节点ip> <主节点port>连上主节点。
演示一:

在客户端使用命令行在127.0.0.1:3679开启master,然后在127.0.0.1:3680/3681开启两个salve

然后关闭master节点,查看从服务节点,发现从服务节点没有自动升级为master,并且他之前从主节点复制来的数据还在,还可以向外提供服务:

之后重新启动master,查看从服务节点,发现从服务节点有重新连接上主服务节点了:

演示二:

恢复到127.0.0.1:3679是master,127.0.0.1:3680/3681是127.0.0.1:3679的两个salve的状态,然后任意重启一个从服务器,观察发现这个服务器结点如果之前没有在配置文件中配置过,那么他将和master没有任何关系了:

“薪火相传”

所谓”薪火相传“,指的是那种一个master连接了一个slave,然后这个slave结点有作为另一个slave的master结点…….依次向链表一样传递下去,这种拓扑的特点是:

  • 只有这个传递链上的第一个master结点具有写的权限,其他的结点都是由读的权限;
  • 这种模式下,减轻了master结点的压力,但是与之而来的问题是越往后的结点同步延时越大;
  • 如果其中一个节点“挂了”,那么他后面的结点就无法同步到最新的数据了
演示:

让127.0.0.1:3679作为127.0.0.1:3680的master,然后让127.0.0.1:3680作为127.0.0.1:3681的master:

“反客为主”

“反客为主”说的就是当主服务器结点“挂了”以后,可以手动将一个从服务器节点指定为主服务器节点,然后让其他的从服务节点从这个新的master上复制:

演示:

在客户端使用命令行在127.0.0.1:3679开启master,然后在127.0.0.1:3680/3681开启两个salve

然后主服务节点突然“挂了”,手动使用命令slaveof no one将原本的 slave 转成 master,停止与其他数据库的同步,然后将其他的slave和这个新的master交互:

这种模式下,在之后以前的主服务器再次启动后,它就与这个新建立的主从关系没有任何关系了。

Redis哨兵模式

       Sentinel(哨兵)是用于监控redis集群中Master状态的工具,是Redis 的高可用性解决方案。Sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master用于自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。其结构如下:

       Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-Slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。Sentinel由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。说的简单点,哨兵模式就是监控+自动版“反客为主”
比如下图的过程:

演示:
首先我们在新建一个sentinel.conf配置文件,配置有关哨兵监控的信息,然后保存退出

让127.0.0.1:3679作为127.0.0.1:3680的master,然后让127.0.0.1:3680作为127.0.0.1:3681的master

一切设置好后,使用redis-sentinel sentinel.conf配置文件路径启动哨兵,让他监控master的状态:

之后关闭master,模拟服务器突然宕机等情况,发现哨兵自动通过投票选举出了新的master,并且把其他从服务器(slave)都拉到了这个新的master“旗下”:

那么如果之前的master重启回来,会不会有两个master冲突? 答案是不会,之前的master会在哨兵模式下变为slave从机

主从复制的原理

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

建立连接

step1:保存主节点信息
从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。(用info replication命令就可以查看)

slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。

step2:建立socket连接
       从节点(slave)每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。
如果连接成功:
* 从节点(slave):为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。

  • 主节点(master):接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。

step3:发送ping命令
       从节点(slave)成为主节点(master)客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
从节点发送ping命令后,可能出现3种情况:
* (1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。

  • (2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。

  • (3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。

step4:身份验证
如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。

从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。

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

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

数据同步阶段

       主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。redis 同步有 2 个命令:syncpsync,前者是 redis 2.8 之前的同步命令,后者是 redis 2.8 为了优化 sync 新设计的命令。

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

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

全量复制和增量复制

       在Redis 2.8以前,从结点向主节点发送的是sync命令同步数据的,这种同步方式是全量复制;但是在Redis 2.8 以后,从结点可以发送psync命令请求同步数据,此时根据主节点当前状态的不同,同步方式可能是全量复制和增量服饰:
* 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,这是一个非常耗费资源的操作。

流程如下:
  1. 发送 psync 命令(spync ? -1)
  2. 主节点根据命令返回 FULLRESYNC
  3. 从节点记录主节点 ID 和 offset
  4. 主节点 bgsave 并保存 RDB 到本地
  5. 主节点发送 RBD 文件到从节点
  6. 从节点收到 RDB 文件并加载到内存中
  7. 主节点在从节点接受数据的期间,将新数据保存到“复制客户端缓冲区”,当从节点加载 RDB 完毕,再发送过去。(如果从节点花费时间过长,将导致缓冲区溢出,最后全量同步失败)
  8. 从节点清空数据后加载 RDB 文件,如果 RDB 文件很大,这一步操作仍然耗时,如果此时客户端访问,将导致数据不一致,可以使用配置slave-server-stale-data 关闭.
  9. 从节点成功加载完 RBD 后,如果开启了 AOF,会立刻做bgrewriteaof。
    以上红色字体的部分是整个全量同步耗时的地方。
  • 增量复制:当从节点正在复制主节点时,如果出现网络闪断和其他异常,从节点会让主节点补发丢失的命令数据,主节点只需要将复制缓冲区的数据发送到从节点就能够保证数据的一致性,相比较全量复制,成本小很多。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行增量复制,仍使用全量复制。
  1. 当从节点出现网络中断,超过了 repl-timeout 时间,主节点就会中断复制连接。
  2. 主节点会将请求的数据写入到“复制积压缓冲区”,默认 1MB。
  3. 当从节点恢复,重新连接上主节点,从节点会将 offset 和主节点 id 发送到主节点。
  4. 主节点校验后,如果偏移量的数后的数据在缓冲区中,就发送 cuntinue 响应 —表示可以进行部分复制。
  5. 主节点将缓冲区的数据发送到从节点,保证主从复制进行正常状态。
命令传播阶段

       数据同步阶段完成后,主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK

心跳机制

主从节点在建立复制后,他们之间维护着长连接并彼此发送心跳命令。

心跳的关键机制如下:

  1. 主从都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制相关客户端信息,主节点的连接状态为 flags = M,从节点的连接状态是 flags = S。
  2. 主节点默认每隔 10 秒对从节点发送 ping 命令,可修改配置 repl-ping-slave-period 控制发送频率。
  3. 从节点在主线程每隔一秒发送replconf ack{offset} 命令,给主节点上报自身当前的复制偏移量。
  4. 主节点收到 replconf 信息后,判断从节点超时时间,如果超过 repl-timeout 60 秒,则判断节点下线。
注意事项:

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

       repl-disable-tcp-nodelay no:该配置作用于命令传播阶段,控制主节点是否禁止与从节点的TCP_NODELAY;默认no,即不禁止TCP_NODELAY。当设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送频率与Linux内核的配置有关,默认配置为40ms。当设置为no时,TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为yes;多数情况使用默认值no。

留言区

还能输入500个字符