Redis学习五:主从复制
本文最后更新于:2024年5月10日 下午
概述
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
使用主从复制
配从不配主
主从复制只需要配置从节点,无需操作主节点
- 配置文件:在从服务器的配置文件中加入:slaveof
<masterIp>
<masterport>
- 启动命令:redis-server启动命令后加入 –slaveof
<masterIp>
<masterport>
- 客户端命令:Redis服务器启动后,直接通过client执行命令:slaveof
<masterIp>
<masterport>
,则该Redis实例成为从节点。
1 初始配置
1、拷贝3份redis.conf
文件,重命名为redis6379.conf
、redis6380.conf
、redis6381.conf
2、开启daemonize yes
;修改pid
文件名;指定对应端口;修改log文件加上端口为后缀;修改Dump.rdb
加端口后缀
3、启动三个redis-server,通过ps -ef | grep redis
查看启动状态

4、通过info replication
查看主从复制信息。基本的配置完成

2 一主二仆
1、在6380、6381上执行SLAVEOF 127.0.0.1 6379
,再次打印信息,可以看到6379成为主,6380、81成为从

2、从机数据复制是从头开始,比如主机set k1、k2、k3,从机从k4开始配置,前面的k123都会复制
3、在主节点上写入数据,在从节点可以读取对应数据;而在从节点写入数据会报错

4、主机挂掉,从机信息:master_link_status:down
原地待命,主机重启后还是主节点,从节点的信息:master_link_status:up
;从机挂掉重启后,主从信息会丢失,需要通过命令SLAVEOF 127.0.0.1 6379
重新指定(也可以通过配置文件修改REPLICATION部分永久生效)
5、从机可以通过SLAVEOF no one
断开主从连接。需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化
3、薪火相传
当前主从节点中的slave可以是另一个主从节点中的master,它同样可以接收其他slave的连接和同步请求,通过此方式可以减轻当前master的读写压力
通过SLAVEOF newIP newPort
更改主从信息,中途变更了主从复制信息后,会清除之前的数据,重新建立拷贝最新的数据,如图6381新的get kkk
其实是通过6380获取的

4、反客为主
通过SLAVEOF no one
将从节点变为master,使当前数据库停止与其他数据库的同步,转成主数据库
复制原理
在初次成功配置主从复制后,可以查看Redis日志:
主机日志:

从机日志:

由日志文件可以看到:主从复制过程大体可以分为3个阶段——连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;下面分别进行介绍。
1、连接建立
该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。
保存主节点信息
从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。
需要注意的是,slaveof
是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof
命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。这个过程中,可以看到从节点打印日志如下:
建立socket连接
从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。如果连接成功,
- 从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。
- 主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。
发送ping命令
从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。
从节点发送ping命令后,可能出现3种情况:
(1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。
(2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。
(3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。
在主节点返回pong情况下,从节点打印日志如下:
身份验证
如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。
如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。
发送从节点端口信息
身份验证之后,从节点会向主节点发送其监听的端口号(本例为6380、6381),主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中;该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用。
2、数据同步
主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。
数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制。需要注意的是,在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端;而到了这一阶段及以后,主从节点互为客户端。原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。
在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。
- 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
- 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。
全量复制
Redis通过psync命令进行全量复制的过程如下:
(1)从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行部分复制;具体判断过程需要在讲述了部分复制原理后再介绍。
(2)主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令
(3)主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态
(4)主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态
(5)如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态
主机的同步日志:
从机的同步日志:
可以看到:从节点接收了来自主节点的175个字节的数据、接受前先flush清除旧数据、没有设置aof所以没调用bgrewriteaof
增量复制
由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。
部分复制的实现,依赖于三个重要的概念:
- 复制偏移量
- 复制积压缓冲区
- 服务器运行ID(runid)
复制偏移量
主从节点各自维护一个复制偏移量offset
,每次完成N个字节的数据复制后,主从节点都会修改各自的偏移量为新的offset+N
。通过offset
可以判断主从节点数据库状态是否保持一致:相同则一致,不同则可以根据offset找出缺少的数据。
例如主节点offset是1000,从节点是500,则部分复制会将501-1000之间的数据进行同步
复制积压缓冲区
主节点在初次配置主从信息开始,就会维护一个固定长度的、默认大小1MB的先进先出(FIFO)队列,用于备份主节点最近发送给从节点的数据。【无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区】
在命令传播阶段,主节点同步写命令到从节点,还会发送给复制积压缓冲区作为备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。
从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
- 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
- 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。
服务器运行ID(runid)
每个Redis节点都有其运行ID,在启动时自动生成
初次主从复制时主节点会将自己的runid发送给从节点,从节点会将runid存起来。 当发生断线重连的时候,主节点根据runid判断是否能进行部分复制:
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
psync命令
PSYNC命令流程图如下:

psync命令的大体流程如下:
- 如果从节点没有复制过任何主节点或执行过slaveof no one命令,从节点就会向主节点发送
psync ? -1
,请求主节点进行数据的全量同步 - 如果前面从节点已经同步过部分数据,此时从节点就会发送
psync <runid> <offset>
命令给主节点,其中runid是上一次主节点的运行ID,offset是当前从节点的复制偏移量
主节点收到psync命令后,会出现以下三种可能:
- 如果主服务器返回
-err
,主服务器的Redis版本低于2.8,无法识别psync命令,此时从服务器会向主服务器发送sync命令,进行完整的数据全量复制 - 主节点返回
fullresync <runid> <offset>
,但可能runid不一致、offset差值大于复制挤压缓冲区长度等,则进行全量复制,其中runid为主节点的运行ID,offset为当前主节点的复制偏移量 - 如果主服务器返回
+continue
,则进行增量复制,等待主节点同步offset差值部分的数据即可
3、命令传播
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。PING发送的频率由repl-ping-slave-period参数控制,单位是秒,默认值是10s。
从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次;命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括:
(1)实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1
(2)检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的
(3)辅助保证从节点的数量和延迟:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。
哨兵模式
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
1、设置为一仆二主,6379带6380、6381
2、编辑一个sentinel.conf
文件,内容为:
1 |
|
3、启动redis-sentinel,指定sentinel.conf
文件
4、通过shutdown关闭主节点6379,等一会儿可以看到,投票选出新的6380作为主节点了

5、再次重启6379后,发现现在已经变成从节点了

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
参考
- 深入学习Redis(3):主从复制
- 尚硅谷Redis