在实际生产环境中,为了避免单点故障,Redis
肯定不会以单机的形式部署,而是会同时部署多个实例。Redis
多机数据库的实现主要有三种方式,分别是主从复制、Sentinel和集群,本文主要会详细阐述主从复制的实现原理。
本文主要内容参考自《Redis设计与实现》
如何使用主从复制
在Redis
中,可以通过SLAVEOF
命令或者设置slaveof
选项,可以让一个服务器去复制另一个服务器。被复制的服务器称为主服务器(master),进行复制的服务器称为从服务器。
旧版复制功能实现
在2.8版本之前,Redis
的复制主要分为同步(sync)和命令传播(command propagate)两个操作:
- 同步:将从服务器的数据库状态更新成主服务器的数据库状态。
- 命令传播:将作用于主服务器的写命令,传播给从服务器进行执行,从而保证主从数据库状态一致。
同步
当从服务器刚执行SLAVEOF
命令时,首先做的就是同步操作,将从服务器的数据库状态更新成主服务器的数据库状态。
从服务器通过向主服务器发送SYNC
命令来完成同步操作,SYNC
命令的执行步骤如下:
- 从服务器向主服务器发送
SYNC
命令。 - 主服务器收到
SYNC
命令之后,开始执行BGSAVE
命令生成RDB
文件。在生成RDB
文件期间,将写命令记录在一个缓冲区中。 - 主服务器发送
RDB
文件文件给从服务器,从服务器载入该RDB
文件。 - 主服务器发送生成
RDB
文件期间的写命令给从服务器,从服务器重放这些命令。此时从服务器状态和主服务器状态一致,同步操作完成。
命令传播
在同步操作完成之后,后续主服务器执行的写命令会以命令传播的方式发送给从服务器,从而保证主从数据库状态一致。
旧版复制功能的缺陷
当初次复制时,旧版复制功能没有任何问题。但是当因为网络问题,主从服务器短暂掉线重连之后,此时依然会触发复制。虽然这种方式可以让主从数据库状态重新一致,但是性能和效率都会很低。因为重连之后的复制,仍然要执行同步操作,生成完整的RDB
文件,从服务器依然要载入该RDB
文件。实际上,在掉线期间,只是丢失了少量写命令,此时执行完整的复制操作,显然效率太低了。
新版复制功能实现
为了解决旧版复制功能在掉线重复制的低效问题,在2.8版本之后,Redis
使用PSYNC
命令代替原来的SYNC
命令来执行同步操作。
PSYNC
命令具有完整重同步(full resynchronization)和部分重同步(patial resynchronization)两种模式:
- 完整重同步:用于处理初次复制的情况。跟旧版的
SYNC
命令基本一致,都是让主服务器生成并发送RDB
文件和在此期间的写命令给从服务器来完成同步操作的。 - 部分重同步:用于处理掉线后的重复制情况。当从服务器掉线后重新连接主服务器,如果条件允许,可以让主服务器只将掉线期间执行的写命令发送给从服务器,从而达到主从数据库状态重新一致。
部分重同步实现
部分重同步功能主要由以下三个部分构成:
- 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量
- 主服务器的复制积压缓冲区(replication backlog)
- 服务器运行ID
复制偏移量
执行复制的双方-主服务器和从服务器会分别维护一个复制偏移量:
- 主服务器每次向从服务器传播N个字节的数据时,就会将自己的复制偏移量加N。
- 从服务器每次收到主服务器传播来的N个字节数据时,也会将自己的复制偏移量加N。
如果主从数据库状态一致,那么它们的复制偏移量一定是相等的,反之,则表示数据库状态不一致。因此当发生掉线重连时,主从服务器的复制偏移量一定不相等。
复制积压缓存区
复制积压缓存区是由主服务器维护的固定长度的先进先出(FIFO)队列,默认大小为1MB
。当主服务器进行命令传播时,不仅会将写命令发送给所有的从服务器,还会将写命令写入复制积压缓冲区中。因此,复制积压缓冲区会保存最近传播的写命令,并且复制积压缓冲区为每个字节记录了相应的复制偏移量。
当从服务器重连之后,从服务器会通过PSYNC
命令将自己的复制偏移量发送给主服务器,主服务器会根据这个偏移量来决定采用哪种同步操作。
- 如果
offset
偏移量之后的数据仍然存在复制积压缓冲区中,那么采用部分重同步。 - 如果
offset
偏移量之后的数据不在复制积压缓冲区中,那么采用完整重同步。
服务器运行ID
每个Redis
服务器,不论是主服务器还是从服务器,都会有自己的运行ID。运行ID在服务器启动的时候生成,由40位随机字符组成。当从服务器进行初次复制时,会将对应的主服务器运行ID也保存下来。通过这个运行ID,就能确定重连上的主服务器是不是掉线之前的主服务器,如果是的话,则尝试之前的部分重同步,反之,则执行完整重同步。
PSYNC命令的实现
PSYNC命令调用方式有两种:
- 如果从服务器之前没有复制过任何主服务器,那么从服务器在开始一次新的复制时,会发送
PSYNC ? -1
命令,主动请求完整重同步。 - 如果从服务器已经复制过主服务器,那么开始一次新的复制时,会发送
PSYNC <runid> <offset>
命令,主服务根据runid
和offset
参数决定使用何时复制方式。
接收到PSYNC
命令的主服务器,可能会返回以下三种结果中的一种给从服务器:
+FULLRESYNC <runid> <offset>
:执行完整重同步。其中runid
表示主服务器的运行ID,从服务器会将其保存下来;offset
是主服务器的复制偏移量,从服务器会将其作为自己的初始偏移量。+CONTINUE
:执行部分重同步。主服务器会将缺少的那部分数据发送给从服务器。-ERR
:复制错误。表示主服务器版本低于2.8,不能识别PSYNC
命令。
主从复制的不足
主从复制解决了数据备份和性能(通过读写分离)的问题,但是还是存在一些不足:
- RDB 文件过大的情况下,同步非常耗时。
- 在一主一从或者一主多从的情况下,如果主服务器挂了,对外提供的服务就不可用了,单点问题没有得到解决。如果每次都是手动把之前的从服务器切换成主服务器,这个比较费时费力,还会造成一定时间的服务不可用。
原创不易,觉得文章写得不错的小伙伴,点个赞👍 鼓励一下吧~
欢迎关注我的开源项目:一款适用于SpringBoot的轻量级HTTP调用框架