转自:伍陆七,
链接:juejin.cn/post/6890349882364657677
在实际生产环境中,为了避免单点故障,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 文件过大的情况下,同步非常耗时。
-
在一主一从或者一主多从的情况下,如果主服务器挂了,对外提供的服务就不可用了,单点问题没有得到解决。如果每次都是手动把之前的从服务器切换成主服务器,这个比较费时费力,还会造成一定时间的服务不可用。