1 概述
复制,主要用于解决单点问题。生产上通常会备用多个节点,以备主节点宕机之患。同时,对于读场景多的情况,也可以做到负载均衡的效果。
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为
主节点
(master),后者称为从节点
(slave);数据的复制是
单向的
,只能由主节点到从节点。
主从复制的作用
主要包括:
数据冗余
:主从复制实现了数据的热备份,是持久化之外的一种数据冗余
方式。故障恢复
:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
。负载均衡
:在主从复制的基础上,配合读写分离
,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多
的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。高可用基石
:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
2 原理
Redis 提供了主从复制模式
,可以保证多台服务器的数据一致性
,且主从服务器之间采用的是「读写分离」
的方式。
- 读操作:主节点、从节点都可以接收;
- 写操作:首先到主节点执行,然后,主节点将写操作
同步
给从节点。
2.1 过程
2.1.1 建立连接
主从复制的开启,完全是在
从节点
发起的;不需要我们在主节点做任何事情。
使用 replicaof
(Redis 5.0 之前使用 slaveof
)命令形成主节点和从节点的关系。
从节点开启主从复制,有3种方式
:
- 配置文件:在
从节点
服务器的配置文件
中加入:replicaof <masterip> <masterport>
- 启动命令:
redis-server
启动命令后加入--replicaof <masterip> <masterport>
- 客户端命令:Redis服务器启动后,直接通过客户端执行命令:
replicaof <masterip> <masterport>
,则该Redis实例成为从节点。
ps: 通过命令
replicaof no one
,可以断开主从复制关系。
通过replicaof
命令启动成功后,从节点会保存主节点的ip
、port
,然后通过定时任务(间隔1s)向主节点发送建立连接请求,主节点通过后,从节点会发送ping
命令验证连接的联通情况,通过之后,若从节点开启了身份验证,则还要经过这一步的验证,验证成功,从节点再向主节点发送端口号,以后主向从发送信息则通过这个端口进行;验证失败,断开连接,重试操作。
上述流程图如下所示:
2.1.2 数据同步
主从复制连接建立起来后,主、从节点就可以开始进行数据同步了。
注意:在
2.8版本之前
只有全量复制,而2.8版本之后
有全量复制
和增量复制
。
- 全量复制:比如第一次同步时.
- 增量复制:只会把主从节点
网络断连期间
,主节点收到的命令,同步给从节点.
这里以2.8版本之后
的Redis为例。从节点发送命令:
psync <runId> <offset>
其中,runId
为主节点的runID
,offset
为从节点的复制进度。首次主从同步,runId
为?
,offset
为-1
。
每个 Redis 服务器在
启动时
都会自动产生
一个随机的 ID
来唯一标识自己。即:runId
。offset
为从节点目前从主节点同步的进度,首次同步时,由于没有,所以会给个默认值-1
。
主节点收到从节点的同步请求后,根据runId
、offset
进行判定,该从节点是否曾经同步过。若同步过,则runId
必等于自己的runId
;否则,从节点为新的节点,需要全量复制进行同步。
runId
验证通过,还要验证其offset
,主节点自身维护的有一个叫做复制积压缓冲区
的东东,用于存储写入的命令及其对应的偏移量,若offset
存在于这个复制积压缓冲区
中,则可进行增量复制
,反之,则需进行全量复制
。
首次同步,主节点返回
FULLRESYNC <主节点的 runID> <主节点目前的复制进度 offset>
从节点收到响应后,会记录下这两个参数。这里有个地方需要注意,FULLRESYNC
响应表示第一次
复制采用的全量复制,也就是说,主节点会把当前所有的数据
都复制给从节点。
接着,主节点执行bgsave
,将RDB文件
发送给从节点,从节点接收到 RDB 文件后,会先清空
当前数据节点,然后加载 RDB 文件。
在主节点将数据同步给从节点的过程中,主节点不会被阻塞
,仍然可以正常接收请求。但是,这些请求中的写操作并没有
记录到刚刚生成的 RDB 文件中。为了保证主从节点的数据一致性,主节点会在内存中用专门的 replication buffer
,记录 RDB 文件生成后收到的所有写操作。
有三个时间间隙
,主节点会将收到的写操作命令,写入到 replication buffer
缓冲区里:
- 主节点生成 RDB 文件期间;
- 主节点发送 RDB 文件给从节点期间;
- 「从节点」加载 RDB 文件期间。
2.1.3 命令传播
从节点完成 RDB 文件的载入后,会回复一个确认消息
给主节点。
接着,主节点将 replication buffer 缓冲区里所记录的写操作命令发送给从节点,从节点执行来自主节点 replication buffer 缓冲区里发来的命令,这时主从节点的数据就一致了。
至此,主从节点的第一次同步的工作就完成了。
主从节点在完成第一次同步后,双方之间就会维护一个 TCP 连接
,用于命令传播。
后续主节点可以通过这个连接继续将写操作命令传播给从节点,然后从节点执行该命令,使得与主节点的数据库状态相同。
而且这个连接是长连接
的,目的是避免频繁的 TCP 连接和断开带来的性能开销。
上面的这个过程被称为基于长连接的命令传播
,通过这种方式来保证第一次同步后的主从节点的数据一致性
。
在命令传播阶段,除了发送写命令,主从节点还维持着
心跳机制
:PING
和REPLCONF ACK
。心跳机制对于主从复制的超时判断、数据安全等有作用。
主->从
:默认每隔 10 秒
发送ping
命令给从节点,判断其存活性和连接状态,由repl-ping-slave-period
参数控制,单位是秒
;从->主
:每隔 1 秒
发送replconf ack <offset>
,上报自己的offset
,实时监测主从节点网络状态,检测命令丢失(没有断线情况下)。
上述全量复制同步
的过程,总结如下图:
如果为增量复制
,其流程如下:
能够完成增量复制
的前提主要有:
- 主节点复制积压缓冲区(repl_backlog_buffer):是一个「环形」缓冲区,默认大小是 1M,用于主从节点断连后,从中找到差异的数据;
- 主从节点各自的复制偏移量(replication offset):标记上面那个缓冲区的同步进度,主从节点都有各自的偏移量,主节点使用
master_repl_offset
来记录自己「写」到的位置,从节点使用slave_repl_offset
来记录自己「读」到的位置。 - 主节点(runId)
那
repl_backlog_buffer
缓冲区是什么时候写入的呢?
在主节点进行命令传播时,不仅会将写命令
发送给从节点,还会将写命令写入到 repl_backlog_buffer
缓冲区里,因此这个缓冲区里会保存着最近传播的写命令。
网络断开后,当从节点重新连上主节点时,从节点会通过 psync 命令
将自己的复制偏移量 slave_repl_offset
发送给主节点,主节点根据自己的 master_repl_offset
和 slave_repl_offset
之间的差距,然后来决定对从节点执行哪种同步操作:
- 如果判断出从节点要读取的数据还在
repl_backlog_buffer
缓冲区里,那么主节点将采用增量复制
的方式; - 如果判断出从节点要读取的数据已经不存在
repl_backlog_buffer
缓冲区里,那么主节点将采用全量复制
的方式。
当主节点在 repl_backlog_buffer
中找到主从节点差异(增量)的数据后,就会将增量的数据写入到 replication buffer
缓冲区,这个缓冲区我们前面也提到过,它是缓存将要传播给从节点的命令。
repl_backlog_buffer
缓行缓冲区的默认大小是 1M
,并且由于它是一个环形缓冲区
,所以当缓冲区写满后,主节点继续写入的话,就会覆盖之前的数据。因此,当主节点的写入速度
远超于从节点的读取速度
,缓冲区的数据一下就会被覆盖。
那么在网络恢复时,如果从节点想读的数据已经被覆盖了,主节点就会采用全量复制
,这个方式比增量复制
的性能损耗要大很多。
因此,为了避免在网络恢复时,主节点频繁地
使用全量复制
的方式,我们应该调整下 repl_backlog_buffer
缓冲区大小,尽可能的大一些,减少出现从节点要读取的数据被覆盖的概率,从而使得主节点采用增量复制
的方式。
一般经验,repl_backlog_buffer
大小采用如下方案设定:
repl-backlog-size = second * write_size_per_second
其中,
second
:为从节点断线后重新连接
上主节点所需的平均时间
(以秒计算)。write_size_per_second
:则是主节点平均每秒产生的写命令数据量大小。
举个例子,如果主节点平均每秒产生
1 MB
的写命令,而从节点断线之后平均要5 秒
才能重新连接主节点。那么
repl_backlog_buffer
大小就不能低于5 MB
,否则新写地命令就会覆盖旧数据了。当然,为了应对一些突发的情况,可以将
repl_backlog_buffer
的大小设置为此基础上的2 倍
,也就是10 MB
。
3 问题
3.1 Redis主从节点时长连接还是短连接?
长连接
3.2 怎么判断 Redis 某个节点是否正常工作?
Redis 判断节点是否正常工作,基本都是通过互相的 ping-pong
心态检测机制,如果有一半以上
的节点去 ping
一个节点的时候没有 pong
回应,集群就会认为这个节点挂掉了,会断开与这个节点的连接。
Redis 主从节点发送的心态间隔是不一样的,而且作用也有一点区别:
- Redis 主节点默认
每隔 10 秒
对从节点发送ping
命令,判断从节点的存活性和连接状态,可通过参数repl-ping-slave-period
控制发送频率。 - Redis 从节点
每隔 1 秒
发送replconf ack{offset}
命令,给主节点上报自身当前的复制偏移量,目的是为了:- 实时监测主从节点网络状态;
- 上报自身复制偏移量, 检查复制数据是否丢失, 如果从节点数据丢失, 再从主节点的复制缓冲区中拉取丢失数据。
3.3 主从复制架构中,过期key如何处理?
主节点处理了一个key或者通过淘汰算法淘汰了一个key,这个时间主节点模拟一条del命令
发送给从节点,从节点收到该命令后,就进行删除key的操作
。
3.4 Redis 是同步复制还是异步复制?
Redis 主节点每次收到写命令之后,先写到内部的缓冲区,然后异步
发送给从节点。
3.5 主从复制中两个 Buffer(replication buffer 、repl backlog buffer)有什么区别?
replication buffer
、repl backlog buffer
区别如下:
- 出现的阶段不一样:
repl backlog buffer
是在增量复制阶段
出现,一个主节点只分配一个repl backlog buffer
;replication buffer
是在全量复制阶段
和增量复制阶段
都会出现,主节点会给每个新连接的从节点,分配一个replication buffer
;
- 这两个 Buffer 都有
大小限制
的,当缓冲区满了之后,发生的事情不一样:- 当
repl backlog buffer
满了,因为是环形结构,会直接覆盖
起始位置数据; - 当
replication buffer
满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制。
- 当
3.6 如何应对主从数据不一致?
为什么会出现主从数据不一致?
主从数据不一致,就是指客户端从从节点中读取到的值和主节点中的最新值并不一致。
之所以会出现主从数据不一致的现象,是因为主从节点间的命令复制
(命令传播)是异步
进行的,所以无法实现强一致性
保证(主从数据时时刻刻保持一致)。
具体来说,在主从节点命令传播阶段
,主节点收到新的写命令后,会发送给从节点。但是,主节点并不会等到从节点实际执行完命令后,再把结果返回给客户端,而是主节点自己在本地执行完命令后,就会向客户端返回结果了。如果从节点还没有执行主节点同步过来的命令,主从节点间的数据就不一致了。
如何如何应对主从数据不一致?
- 第一种方法,尽量保证主从节点间的网络连接状况良好,避免主从节点在不同的机房。
- 第二种方法,可以开发一个外部程序来监控主从节点间的复制进度。具体做法:
- Redis 的
INFO replication
命令可以查看主节点接收写命令的进度信息(master_repl_offset
)和从节点复制写命令的进度信息(slave_repl_offset
),所以,我们就可以开发一个监控程序,先用INFO replication
命令查到主、从节点的进度,然后,我们用master_repl_offset
减去slave_repl_offset
,这样就能得到从节点和主节点间的复制进度差值了。 - 如果某个从节点的进度差值
大于
我们预设的阈值,我们可以让客户端不再和这个从节点连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从节点都不能连接的情况,我们需要把复制进度差值的阈值
设置得大
一些。
- Redis 的