Redis的主从复制
1、主从复制概述
在Redis客户端通过info replication可以查看与复制相关的状态,对于了解主从节点当前状态,以及解决出现的问题都会有帮助
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点复制到从节点
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或者没有从节点),但一个从节点只能由一个主节点
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由主节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提供Redis服务器的并发量
- 高可用基石:除了上述作用外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redsi高可用的基础
2、如何使用主从复制
为了更直观的理解主从复制,在介绍其内部原理之前,先说明我们需要如何操作才能开启主从复制
1、建立复制
需要注意,主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情
从节点开启主从复制,有三种方式:
-
配置文件,在从机的Redis配置文件中加入配置
################################# REPLICATION ################################# # Master-Slave replication. Use slaveof to make a Redis instance a copy of # another Redis server. A few things to understand ASAP about Redis replication. # # 1) Redis replication is asynchronous, but you can configure a master to # stop accepting writes if it appears to be not connected with at least # a given number of slaves. # 2) Redis slaves are able to perform a partial resynchronization with the # master if the replication link is lost for a relatively small amount of # time. You may want to configure the replication backlog size (see the next # sections of this file) with a sensible value depending on your needs. # 3) Replication is automatic and does not need user intervention. After a # network partition slaves automatically try to reconnect to masters # and resynchronize with them. # # 如以下配置 # slaveof 192.168.3.51 6379 # slaveof <masterip> <masterport>
-
启动命令
redis-server 启动命令后加入 --salveof
-
客户端命令
在Redis客户端中下使用命令 salveof
上述三种方式是等效的。但是第二、三种方式,每当从节点宕机或主动与主节点断开连接后,再次启动需要从新与主节点建立主从关系
2、实例
准备工作:启动两个节点
方便起见,实验所使用的的主从节点是在一台机器上的不同Redis实例,其中主节点监听6379端口,从节点监听6380端口;从节点监听的端口号可以在配置文件中修改。
当两个Redis节点启动后,默认都是主节点。
建立主从关系
此时在6380节点执行slaveof命令,是之变为从节点
-
首先在从节点查询一个不存在的key
-
然后在主节点中增加这个key
-
此时在从节点中再次查询这个key,会发现主节点的操作已经同步到从节点
-
从主节点中删除这个key
-
此时在从节点中查询这个key,会发现主节点的操作已经同步至从节点
3、断开主从连接
通过slaveof
从节点执行slaveof no one后,打印日志如下所示,可以看出断开复制后,从节点又变回成主节点。
主节点打印日志如下:
3、主从复制实现原理
上面一节中,介绍了如何操作可以建立主从关系,本小节将介绍主从复制的实现原理。
主从复制过程大体可以分为三个阶段:
- 连接建立阶段(即准备阶段)
- 数据同步阶段
- 命令传播阶段
1、连接建立阶段
该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备
1、保存主节点信息
从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。
需要注意的是,slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。
在这个过程中,可以看到主节点打印日志如下:
2、建立Socket连接
从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建Socket连接。如果连接成功,则:
- 从节点:为该Socket建立一个专门处理复制工作的文件时间处理器,负责后续的复制工作,如接受RDB文件,接受命令传播等。
- 主节点:接收到从节点的Socket连接后(即accept后),为该Socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。
在这个过程中,可以看到从节点打印日志如下:
3、发送Ping命令
从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查Socket是否可用、以及主节点当前是否能够处理请求。
从节点发送ping命令后,可能出现如下3中情况:
-
返回pong:说明Socket连接正常,且主节点当前可以处理请求,复制过程继续
-
超时:一定时间后,从节点仍未收到主节点的回复,说明Socket连接不可用,则从节点断开Socket连接,并重连
-
返回pong以外的结果:如果主节点返回其他的结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开Socket连接,并重连
在主节点返回pong的情况下,从节点打印日志如下:
4、身份验证
如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。
如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续。如果不一致,则从节点段开Socket连接,并重连。
5、发送从节点端口信息
身份验证之后,从节点会向主节点发送其监听的端口号(前述例子中为6380),主节点将信息保存到该从节点对应的客户端的slave_listening_port中,该端口信息除了在主节点中执行info Replication时现实以外,没有其他作用。
2、数据同步阶段
主从节点之间的连接建立后,便可以开始进行数据同步,该阶段可以理解为从节点数据的初始化。具体执行的方式是,从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。
数据同步阶段是主从复制最核心的阶段,根据主从节点当前状态的不同,可以分为全量复制和部分复制,下面会有一章专门讲解着两种复制方式以及psync命令的执行过程,这里不再详述。
需要注意的是,在数据同步阶段之前,从节点是主节点的客户端,主节点不是从节点的客户端。而到了这一阶段及以后,主从节点互为客户端,原因在于,在此之前,主节点只需要响应从节点的请求即可,不需要主动发送请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。
3、命令传播阶段
数据同步阶段完成后,主从节点进入命令传播阶段,在这个阶段主节点将自己执行的写命令发送给从节点。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。由于心跳机制的原理涉及部分复制,因此将在介绍了部分复制的相关内容后独单介绍该心跳机制。
延迟与不一致
需要注意的是,命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;因此实际上主从节点之间很难保持实时的一致性,延迟在所难免。数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。
repl-disable-tcp-nodelay no:该配置作用于命令传播阶段,控制主节点是否禁止与从节点的TCP_NODELAY;默认为no,即不禁止TCP_NODELAY。当设置为yes时,TCP会对包进行合并从而减少带宽,但是发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送的频率与Linux内核的配置有关,默认配置为40ms,当设置为no时,TCP会立马将主节点的数据发送给从节点,带宽增加但延迟变小。
一般来说,只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状态不好时,才会设置为yes,多数情况下使用默认值no
4、【数据同步阶段】全量复制和部分复制
在Redis2.8之前,从节点向主节点发送sync命令请求同步数据,此时的同步方式是全量复制;在Redis2.8及以后,从节点可以发送psync命令请求数据同步,此时根据主从节点当前状态的不同,同步方式可能是全量复制,或部分复制。后文介绍以Redis2.8及以后版本为例。
- 全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作
- 部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整的保存中断期间执行的的写命令,则无法进行部分复制,仍使用全量复制
1、全量复制
Redis通过psync命令进行全量复制的过程如下:
- 从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;具体判断过程需要在讲述了部分复制原理后再介绍
- 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令
- 主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清楚自己的旧数据,然后载入接受的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态
- 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态
- 如果从节点开启AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态
下面试执行全量复制时,主从节点打印的日志;可以看出日志内容与上述步骤是完全对应的。
主节点的打印日志如下:
从节点的打印日志如下:
其中,有几点需要注意:从节点接受了来自主节点的89260个字节的数据;从节点在载入主节点的数据之前要先将老数据清除;从节点在同步完数据后,调用了bgrewriteaof
通过全量复制的过程可以看出,全量复制是非常重型的操作:
- 主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的;关于bgsave的性能问题,可以参考深入学习Redis(2):持久化
- 主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
- 从节点清空旧数据,载入新RDB文件的过程是阻塞的,无法响应客户端的命令,如果从节点执行bgrewriteaof,也会带来额外的消耗
2、部分复制
由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步
部分复制的实现,依赖于三个重要的概念:
-
复制偏移量
主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播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-backing-size);例如:如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制
从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:
- 如果offset偏移量之后的数据,仍然都积压在复制及压缓冲区里,则执行部分缓存
- 如果offset偏移量之后的数据,已经不在复制积压缓冲区中(数据已被挤出),则执行全量复制
-
服务器运行ID(runid)
每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),有40个随机数的十六进制字符组成,runid用来唯一识别一个Redis节点。同过info Server命令,可以查看节点的runid。
主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制
- 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还有看offset和复制积压缓冲区的情况)
- 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制
3、psync命令的执行
在了解了复制偏移量、复制积压缓冲区、节点运行runid之后,本节将介绍psync命令的参数和返回值,从而说明psync命令执行过程中,主从节点是如何确定使用全量复制还是部分复制的。
psync命令在执行过程可以参见下图(图片来源:《Redis设计与实现》)
- 首先,从节点根据当前状态,决定如何调用psync命令:
- 如果从节点之前为执行过slaveof或最近执行了slaveof no one,则从节点发哦是哪个命令为psync ? -1,向主节点请求全量复制
- 如果从节点之前执行了slaveof,则发送命令为psync
,其中,runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量
- 主节点根据收到的psync命令,即当前服务器的状态,决定执行全量复制还是部分复制:
- 如果主节点的版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制
- 如果主节点版本够新,且runid与从节点发送的runid相同,且从节点发送的offset之后的数据在复制积压缓冲区中,则回复+CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可
- 如果主节点版本够新,但是runid与从节点发送的runid不同,或从节点发送的offset之后的数据已经不在复制积压缓冲区中(在队列中被挤出了),则回复+FULLRESYNC
,表示将进行全量复制,其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用
4、部分复制演示
在下面的演示中,网络中断几分钟后恢复,断开连接的主从节点进行了部分复制;为了便于模拟网络中断,本例中的主从节点在局域网中的两台机器上
-
网络中断
网络中断一段时间后,主节点和从节点都会发现失去了与对方的连接(关于主从节点对超时的判断机制,后面会有说明);此后,从节点便开始执行对主节点的重连,由于此时网络还没有恢复,重连失败,从节点会一直尝试重连
主节点日志如下:
从节点日志如下:
-
网络恢复
网络恢复后,从节点连接主节点成功,并请求进行部分复制,主节点接受请求后,二者进行部分复制以同步数据
主节点日志如下:
从节点日志如下:
5、【命令传播阶段】心跳机制
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用
1、主 -> 从:PING
每隔指定的时间:主节点会向从节点发送Ping命令,这个Ping命令的作用,主要是为了让从节点进行超时判断。
Ping发送的频率由repl-ping-slave-period参数控制,单位是秒,默认值是10s。
关于该Ping命令究竟是由主节点发给从节点,还是相反,有一些争议;因为在Redis官方文档中,对该数据的注释中说明是从节点向主节点发送Ping命令,如下图所示
但是根据该参数的名称(含有ping-slave),以及代码实现,我认为该ping命令是主节点发给从节点的。相关代码如下:
2、从 -> 主:REPLCONF ACK
在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,频率是每次一秒;命令格式为:REPLCONF ACK,其中offset值从节点保存的复制偏移量。REPLCONF ACK命令的作用包括:
-
试试检测主从节点的网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1,如下如所示:
-
检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。注意,offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情况,区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的
-
辅助保证从节点的数量和延迟: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值
6、总结
下面回顾下本文的主要内容:
- 主从复制的作用:宏观的了解主从复制是为了解决什么样的问题,即数据冗余,故障恢复,读负载均衡等
- 主从复制的命令操作:即slaveof命令
- 主从复制的原理:主动复制包括了连接建立阶段,数据同步阶段,命令传播阶段;其中数据同步阶段有全量复制和部分复制两种数据同步方式;命令传播阶段,主从节点直接有Ping和REPLCONF ACK命令互相进行心跳检测
主从复制虽然解决或缓解了数据冗余,故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化,写操作无法负载均衡;存储能力受到了单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。