Redis主从复制及其实现原理
首先,简单介绍一下什么是Redis主从复制
。
假如我们服务中用到了Redis,并且只有一台Redis服务器。如果某个时刻该Redis服务挂了,那么会导致整个服务的Redis不可用,在此期间,大量的请求将会直接打到数据库(mysql),导致数据库压力陡增,严重的可能导致数据库直接挂掉。这种情况,我们称之为单点故障
。
为了应对Redis的单点故障问题,于是就有了Redis的主从复制,也就是服务中存在多台Redis服务器,或者称之为Redis节点。所有Redis节点的数据都是一样的,其中一个Redis节点作为主节点(主库),其余的Redis节点作为该节点的从节点(从库)。这就是Redis
提供的主从模式
。
然后主从模式下采用读写分离
:
- 读操作:主库从库均可执行读操作,主要从从库读。
- 写操作:仅主库可以执行写操作,然后把数据同步给各个从库,保证主从数据一致性。
通过上面的介绍,相信大家应该了解了什么是Redis的主从复制。
那么,如何实现Redis的主从复制功能呢?
因为Redis提供了主从模式的功能,所以我们只需要在Redis节点的配置文件(redis.conf
)配置相关项即可。
- 主节点无需做任何修改
- 从节点需要修改 redis.conf 配置文件的相关配置,如下:
# 配置主节点的ip和端口 注意:Redis5.0之后 slaveof 改成了replicaof
slaveof 127.0.0.1 6379
# 是否只读 redis2.6及之后 从节点默认只读
slave-read-only yes
# 主节点登录密码(如果主节点没有登录密码,则无需配置此选项)
masterauth xxxxxx
从节点配置好以上信息之后,先启动主节点,再启动从节点,主从节点便会建立联系,开始执行主从同步操作。
那么,这个主从同步流程具体是如何执行的呢?咱们且接着往下看。
首先,我们看一下整体的流程图,之后再逐步介绍都干了什么:
第一步:主从节点建立连接,协商数据同步,为全量复制做准备
- 主从节点第一次建立连接的时候,会执行全量复制,即把主节点的全部数据都复制到从节点,所以称之为全量复制。
- 通过上图第一步可以看到,从节点发送
psync ? -1
命令给主节点,表示要进行复制。
其中?代表主节点的 runId;-1 代表复制进度 offset。
runId:每个Redis节点启动时都会生成一个唯一的随机id,用来标识这个Redis节点。当从库第一次向主库发起复制请求时,是不知道主库的runId的,所以将runId设置为问号?
offset:复制进度为-1表示是第一次复制。 - 主节点收到从节点的
psync
命令后,会通过FULLRESYNC
命令进行响应,并将自己的runId
和当前的复制进度offset
发送给从节点。
第二步:主节点将全部数据通过RDB文件发送给从节点
- 第二步中,主节点会执行
bgsave
命令,生成RDB
文件,并将 RDB 文件发送给从节点。 - 从节点收到 RDB 文件后,会先清空自己的数据(防止主从数据不一致),然后完成 RDB 文件的数据加载。
- 主节点给从节点同步数据的过程中,RDB 文件的生成是通过子进程完成的,主节点并不会被阻塞(
fork子进程会阻塞
)。在此期间主节点仍然会正常接收请求,但是这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中,为了保证主从数据一致性,主节点会把 RDB 文件生成后的写操作记录到内存中的一块缓冲区,即replication buffer
。
第三步:主节点将第二步执行过程中接收的写请求发送给从节点,从节点重新执行这些操作,实现主从同步。
到此,Redis的主从同步具体执行流程,我们已经比较清楚了。
值得注意的是:主从复制的过程中,Redis会 fork 子进程生成 RDB 文件,这个 fork 子进程的操作会阻塞 Redis 主线程。假如从节点非常多的时候,会导致主节点忙于 fork 子进程,进而导致主节点阻塞,这时候该怎么办呢?咱们且接着往下看:
这时候,我们可以通过“主-从-从”的模式
,将主节点生成和发送 RDB 文件的压力以级联的方式分散到从节点
。
说简单点就是:可以选择一个配置相对较高的从节点,作为其他从节点的主节点,如下图所示:
可以看到,从节点2和从节点3的主节点就不再设置成主节点了,而是设置成从节点1。这样便可以有效缓解主节点的多从复制压力了。
那么,除了上述问题之外,还存在其他问题吗?答案是肯定的。
主从节点建立连接之后,他们之间会一直维护着一个长连接,避免频繁建立连接的开销。但是,假如这个连接断开了会怎么样呢?
Redis2.8之前,主从节点断开重连之后会执行全量复制,显然全量复制开销过大,所以在Redis2.8之后就被淘汰了,升级成了增量复制。那么,什么是增量复制呢?
增量复制
断连重连之后,从节点不会复制所有的主节点数据,只会复制断连之后这段时间的写操作数据,这就是增量复制,顾名思义,就是只复制断连后增加的那部分数据量。
增量复制的实现依赖于一个环形缓冲区 repl_backlog_buffer
,只要有从节点在,这个环形缓冲区就会存在。主节点会在这个环形缓冲区记录自己写到的位置,从节点会在这个环形缓冲区记录自己已经读到的位置
。如下图所示:
主节点的所有写操作除了复制给从节点之外,也会在这个环形缓冲区记录一份,也就是上面说到的主节点会在这个环形缓冲区记录自己写到的位置,只有预先缓存了这些写操作,当从节点断连之后,又恢复连接然后发送 psync {runId} {offset}
命令执行主从同步。主节点才能通过 {offset}
参数在环形缓存区 repl_backlog_buffer
中找到从节点断连的位置,然后发送增量数据(offset位置到主节点写到的位置之间的数据)给从节点。
注意:假如从节点断连时的读位置 offset
已经被主节点的写位置覆盖了,那么此时便无法执行增量复制操作了,只能再执行一次全量复制了。所以,为了尽量避免断连之后的全量复制,可通过 repl_backlog_size
这个参数合理设置 repl_backlog_buffer
的大小,减少发生全量复制的概率。
计算公式:缓冲空间大小 = 主节点写入命令速度 * 操作大小 - 主从节点间网络传输命令速度 * 操作大小
为应对突发情况,可将 repl_backlog_size
设置为缓冲空间大小的2倍。
即:repl_backlog_size = 缓冲空间大小 * 2
接着上面的说,增量数据是如何发送给从节点的呢,这就用到了 replication buffer
。
关于 replication buffer
的介绍
- Redis和客户端或者从节点通信,都会分配一个对应的内存缓冲,通过这个内存缓冲完成数据交互,如果连接断开,这个内存缓冲也会不复存在。
- 当和从节点进行交互的时候,这个内存缓冲专门用于复制主节点的写操作到从节点,所以叫
replication buffer
。如果从节点处理数据特别慢,那么这个replication buffer
会持续增长,可能导致OOM。 - 这个内存缓冲的大小可通过
client-output-buffer-limit
参数限制,如果超过这个限制,主节点会强行断开这个从节点(客户端)的连接。此时复制中断,如果之后再继续发起复制请求,可能会导致恶性循环,引发复制风暴,需格外注意。
以上内容参考了极客时间Redis专栏介绍以及Kaito大神的相关说明。
到此,Redis主从复制相关介绍就结束了。
这种模式可以解决Redis的单点故障问题,提高Redis的可用性。但是缺点也是很明显的,假如主节点挂了,从节点是无法自动升级成主节点的,这个过程需要人工处理,在此期间,Redis无法对外提供写操作。此时,Redis哨兵模式就该登场了,待更新…
此外,整个Redis的主从节点数据都是一样的,也就是冗余的,会造成一定的资源浪费。此时,Redis集群模式就该登场了,详见Redis集群搭建