主从同步简介
redis集群的搭建方式有一主一从,一主多从,多主多从等方式,目前我们介绍主从的方式,就从一主一从开始切入。
主从复制的作用如下:
- 数据备份:主从同步实现了redis数据的热备份,是持久化之外的另一种数据冗余方式。增强了数据安全性。
- 故障恢复:从节点备份了主节点数据的所有数据,当主节点宕机时,从节点可以即刻顶上,无缝衔接,实现故障的快速恢复,也算是一种服务冗余。
- 读写分离:在主从复制的基础上上,通过负载均衡等方式,可以由主节点负责写入,从节点提供读服务。在读多写少的场景下,选择一主多从的集群方式,大大提高redis的服务器的并发量。
- 高可用、高并发基石:主从复制,主从通信是redis集群和哨兵模式的实现的基础。
redis集群搭建
主从复制的开启,完全是从节点上的发起的,无需主节点做任何操作。
开启主从复制的方式
- 配置文件:在redis的配置文件中增加配置:slaveof 主节点ip:port
- redis启动命令:redis-server 启动命令加入 -- slaveof
- 客户端发送命令:slaveof ip port
总结:上边三种方式是等效的,只是后两种方式在从节点重启后需再次执行。
客户端命令主从复制示例
准备工作:启动两个redis节点
端口规划:6379 6380
启动时两个都是主节点,启动后计划6379实例为主节点,6380设置为从节点。
6379为默认端口。6380端口实例需要在配置文件中修改端口号。
# Accept connections on the specified port,default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
port 6380
建立主从复制关系
从节点执行命令:
redis-cli 6380 #连接客户端
127.0.0.1:6380> slaveof 127.0.0.1 63729 #建立主从复制关系
主从同步演示效果
- 从节点查询一个不存在的key(mykey)
- mykey不存在
- 主节点增加mykey
- 从节点查询mykey
- 从节点同样可以查询到mykey的值,说明数据已经从主节点同步过来了。
- 主节点删除mykey
- 从节点查询mykey
- 从节点数据也不存在了,说明从节点删除数据也同步成功了。
断开复制
通过slaveof命令建立的主从同步关系,可以通过slaveof no one 命令断开。
:::color5 ⚠️ 从节点与主节点断开以后,从节点的已存在数据不会被删除,只是不再接收主节点同步的数据。
:::
主从复制的核心原理
redis主从同步方式
redis集群可以使用主从同步和从从同步,主要的同步方式有全量复制和部分复制。
- 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。
- 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍要使用全量复制。
初次建立主从复制关系(全量复制):
- 从节点发送slaveof命令,主节点接收到命令,做一次bgsave,将全量数据写入到rdb文件,同时把后续的操作记录写到复制缓冲区中。
- rdb写入完成后全量同步到从节点。从节点接受完成后将rdb文件全量加载到内存中。
- 加载完成后,再通知主节点将操作记录缓冲区中的数据也同步到从节点,进行重放就完成了同步过程。
- 后续的增量数据都是通过同步数据的操作记录完成的,类似mysql的binlog日志。
建立主从同步关系后异常断开(部分复制):
- 从节点每秒尝试与主节点建立socker连接。
- 建立连接时发送命令 psync {runId} {offset},需要带上runid和offser信息。
- 主节点接收到offset信息后,判断offset是否在复制积压缓冲区内?
- 在缓冲区内则将缓冲区中的数据发送到从节点,从节点完成增量数据同步。
- 不在缓冲区内,则执行全量复制步骤。
名词解释
:::color2 📝 redis sever的runId
每个redis实例启动时,都会生成一个随机ID(每次启动都是不同的),由40个随机的十六进制字符组成,runId用来标识唯一的redis节点。
:::
# 通过info server命令查询run_id
redis-cli info server | grep run_id
主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。
:::color2 📝 复制偏移量offset
- 主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数。
- 主节点每次向从节点传输N个字节数据时,主节点的offset就增加N。
- 从节点每次从主节点接收到N个字节数据时,从节点的offset就增加N。
:::
offset的复制偏移量用途
offset用于判断主从节点的数据是否一致,如果两者offset相同,则数据相同;如果不同,则不一致,此时则可以依据offset判断两者数据相差的部分。
例如:如果主节点的offset是1000,从节点的offset是500,则部分复制就可以将501-100字节的数据传输到字节点即可同步不一致的数据。
:::color2 📝 复制积压缓冲区(repl-backlog-buffer)
复制积压缓冲区是由主节点进行维护的、固定队列的、先进先出的队列,默认为1MB;
当主从关系建立时,master创建一个积压缓冲区,其作用是备份主节点最近接收到的redis命令,后续会发送给从节点的数据。
无论主节点有一个从节点还是有多个从节点,主节点都只需要一个复制缓冲区。
:::
在命令传输阶段,主节点除了将写命令发送给从节点之外,还会在复制缓冲区内备份写命令。
除了储存写命令,复制缓冲区中还存储了其中的每个命令对应的复制偏移量(offset)。
由于复制缓冲区长度是固定有限的,因此可以备份的写命令也十分有限,当主从节点offset的差距超过缓冲区的长度时,将无法执行部分复制,只能执行全量复制。
主从同步流程
slaveof命令执行流程
主节点接收到的从节点发送的slaveof命令后,首先决定是使用全量复制还是部分复制。
- 从节点根据当前状态,决定如何调用 psync命令:
- 如果从节点从未执行过 slaveof 或者 执行过 slaveof no one ,从节点发送命令为 psync ? -1;使用全量复制。
- 如果从节点执行slaveof,则发送命令为 psync {runId} {offset} ,其中 runid为上次复制的主节点的 runid,offset 为从节点保存的复制偏移量。
- 主节点接收到从节点发送的 psync 命令后,根据命令数据和当前服务状态,判断执行全量复制还是部分复制。
- 主要主节点版本低于 Redis2.8 ,则回复 -ERR ,此时从节点重新发送 psync命令执行全量复制,因为 Redis2.8 版本之前只有全量复制的模式,没有部分复制的模式。
- 如果主节点在2.8版本之后,且判断runid和主节点的runid是否相同,且判断offset是否在主节点的复制缓冲区内,如果都满足,则回复+COUTINUE,表示使用部分复制模式,从主节点发送部分未同步数据即可。
- 如果上述条件存在未满足的,则回复+FULLRESYNC {runid} {offset},表示进行全量复制,runid为主节点的runid,offset为主节点的当前offset。从节点需要存储这两个值,供同步数据使用。
主从复制流程
主从复制大致可以分三个流程
- 主从连接建立阶段
- 数据同步阶段
- 命令传输阶段
连接建立阶段
- 保存主节点信息
- 从节点执行slaveof命令后,先将主节点的host 和 port信息保存,用于之后到主从节点通信,然后既返回OK,复制操作是异步执行的。
- 建立socket连接
- slaveof 命令执行后,从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可信息,便会根据主节点的ip和port,创建socket连接。
- 创建socket连接时,如果主节点存在密码,则需要从节点中设置masterauth选项,从节点需要向主节点进行身证;没有配置该选项,则不需要验证。如果身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。
- socket连接创建成功
- 从节点: 为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收主节点传输的命令等。
- 主节点:socket建立连接(即完成accept操作)后,主节点会为这个连接创建一个专门的客户端状态。此时,从节点就是连接至主节点的一个客户端。后续交互都是以从节点向主节点发送命令请求的方式进行。
- 从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前能否处理请求。
- 从节点发送ping命令后,可能出现3种情况:
- 返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。
- 超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。
- 返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。
- 从节点发送ping命令后,可能出现3种情况:
- 发送从节点端口信息
socket成功建立之后,从节点会向主节点发送其监听的端口号(前述例子中为6380),主节点将该信息保存到从节点对应的客户端的slave_listening_port字段中;
:::tips 🤪该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用。
:::
数据同步阶段
主从节点之间的连接建立以后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。同步方式需要判断全量复制还是部分复制,判断过程见4.1所示。下边详细介绍全量复制过程和部分复制过程。
全量复制过程
Redis通过psync命令进行全量复制的过程如下:
- 从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求, 但主节点判断无法进行部分复制;
- 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。
- 主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点接收完成之后,首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态
- 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态
- 如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态
下面是执行全量复制时,主从节点打印的日志;可以看出日志内容与上述步骤是完全对应的。
主节点的打印日志如下:
从节点打印日志如下图所示:
:::warning ⚠️ 从节点接收了来自主节点的89260个字节的数据,在载入主节点的数据之前要先将老数据清除;
:::
:::color1 📝 从节点在同步完数据后,调用了bgrewriteaof。通过全量复制的过程可以看出,全量复制是非常重型的操作:
- 性能损耗:主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页 表复制)、硬盘IO的。
- 带宽占用:主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗。
- 停服载入:从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗。
:::
:::danger 🤔 全量复制的弊端:
触发场景:
- 新创建的slave,从主机master同步数据。
- 刚宕机一小会的slave,从主机master同步数据。
前者新建的slave则从主机master全量同步数据,这没啥问题。但是后者slave可能只与master存在小量的数据差异,要是全量同步肯定没有只同步差异(部分复制)少量数据的性能高。
:::
部分复制过程
部分复制是Redis为了降低全量复制高成本而采取的优化方法,通过使用psync {runId} {offset}
命令来实现。当从节点复制主节点数据期间发生网络中断或命令丢失等异常,从节点会请求主节点补充丢失的数据。如果主节点的复制缓冲区保存了这些丢失的数据,它将直接传送至从节点,确保主从数据一致性。相比全量复制,这种只补充差异数据的方式大大减少了数据传输量,从而降低了开销。
部分复制步骤:
- 出现网络中断超时(超过repl-timeout),主节点与从节点的复制连接断开,视为从节点故障。
- socket连接中断期间,主节点正常服务,但命令无法传至从节点。主节点最近写操作可暂存在复制积压缓冲区( repl-backlog-buffer )中,默认大小1MB,以备连接恢复后进行部分复制。
- 网络恢复后,从节点主动重连主节点。
- 主从节点重新建立连接后,从节点用之前记录的偏移量和主节点runId作为psync指令,请求缺失数据补发。
- 主节点确认psync指令中的runId匹配且所需偏移量后的数据仍在其缓冲区中,则同意部分复制,发送+CONTINUE响应。
- 主节点依据偏移量提供缓冲区数据给从节点,确保主从节点数据一致,主从复制流程恢复正常。
命令传输阶段
数据同步阶段完成后,主从节点进入命令传输阶段;
在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。
在命令传输阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。 心跳机制对于主从复制的超时判断、数据安全等有作用。
- 主节点向从节点发送ping命令
每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行网络超时判断。 PING发送的频率由 repl-ping-slave-period 参数控制,单位是秒,默认值是10s。
:::tips ⚠️ 关于该PING命令究竟是由主节点发给从节点,还是从节点发到主节点,有一些争议;
因为在Redis的官方文档中,对该参数的注释中说明是从节点向主节点发送PING命令,如下所
示:
:::
# Slaves send PINGs to server in a predefined innterval.It'spossibletochange
#this interval with the repl_ping_slave_period option.Thedefault value is 10
#seconds.
#
# repl-ping-slave-period 10
:::tips 但是通过源码可以看到, PING命令是主节点会向从节点发送.
可能的原因是:代码的迭代和注释的迭代,没有完全同步。 可能早期是从发给主,后面改成了主
发从,而并没有配套修改注释。
从理论层面看,大部份组件的集群模式都是一主多副本,通信方式也是从节点请求主节点,主从节点耦合度更低,是否同步、何时同步都由从节点控制。
从实际效果来看,应该主节点发到从节点这种方案更好一些,因为命令延迟更低,同步速度更快。
:::
- 从节点向主节点发送REPLCONF ACK命令
在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次; 命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。
REPLCONF ACK命令的作用包括:
- 实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1,:
- 检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失 (如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。
:::tips ⚠️ *offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。 *
:::
- 辅助保证从节点的数量和延迟:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参 数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过 高。
:::tips 🧸 例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。
:::
本文由博客一文多发平台 OpenWrite 发布!