我们可以通过Redis提供的slaveof命令让一个服务器去复制另一个服务器,我们成被复制的服务器为主服务器,进行复制的服务器称为从服务器。那么Redis是如何实现服务器复制的呢?主从服务器之间如何保持数据一致性的呢?
一.Redis复制功能的实现
Redis的复制功能分为同步和命令传播,同步用于将从服务器的数据库状态更新至主服务器当前数据库状态;命令传播用于主服务器状态修改时,传播命令到从服务器,解决主从一致性问题。
1.同步
当从服务器发送slaveof命令复制主服务器时,从服务器会执行同步操作,将服务器数据库状态更新至主服务器当前状态。从服务器通过向主服务器发送sync命令执行同步操作。
(1)首先,从服务器向主服务器发送sync命令
(2)主服务器收到sync命令后,执行bgsave命令(生成RDB快照命令),并使用一个缓冲区记录从现在开始执行的所有写命令
(3)主服务器执行完bgsave命令后,会将生成的快照文件发送给从服务器,从服务器通过载入RDB文件,将数据库状态更新至主服务器状态
(4)主服务器将记录在缓冲区里面的所有写命令发送给从服务器
以上几步实现了主从服务器的同步功能
2.命令传播
当主服务器的数据被修改时,需要通知从服务器,否则主从服务器之间的数据就不一致了,这里Redis通过命令传播来实现。
主服务器会将自己执行的写命令发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器数据就达到了一致的状态。
二.Redis复制机制的改进
老版本的Redis复制有几个缺陷,比如从服务器如果断线了几秒钟之后重新连接到主服务器,这时候主从服务器数据处于不一致状态,为了达到一致,需要从服务器重新复制主服务器,执行同步操作,这一过程其实非常耗时,设计RDB文件的生成,RDB文件的传播,RDB文件的载入。如果我们能回复这几秒钟的数据,就可以避免这一系列操作。基于这一缺陷,Redis在2.8版本之后加入了一个优化。
Redis从2.8版本开始,使用psync命令代替sync命令实现同步操作。psync命令具有完整重同步和部分重同步两种模式。其中完整重同步用于初次复制情况,和sync命令基本一致。部分重同步则是为了解决从服务器断线后重新复制的情况。部分重同步会使用缓冲区保存断线这段时间的命令,通过发送这部分命令来避免执行完全重同步,提高整体性能。
部分重同步的实现
部分重同步由以下三个部分构成:主服务器的复制偏移量和从服务器的复制偏移量,主服务器的复制积压缓冲区,服务器运行ID着三部分。
1.复制偏移量
主从服务器分别维护一个复制偏移量,主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量+N,从服务器每次接收到N个字节的数据时,就将自己的复制偏移量+N。通过对比主从服务器的复制偏移量,程序可以很容易的知道主从服务器是否处于一致状态。如果主从服务器处于一致状态,那么两者的偏移量总是相同的,如果两者的偏移量不相同,说明主从服务器状态不一致。
2.复制积压缓冲区
复制积压缓冲区是主服务器维护的一个固定长度的先进先出队列,默认1MB。当主服务器进行命令传播时,它不仅会将命令发送给所有从服务器,还会将命令入队到复制积压缓冲区里面。因此复制积压缓冲区里面保存着一部分最近传播的写命令,并且缓冲区会为队列中的每个字节记录响应的复制偏移量,当从服务器重新连接上主服务器时,从服务器会通过psync命令将自己的复制偏移量offset发送给主服务器,主服务器通过偏移量是否还在缓冲区来决定是否完整重同步还是部分重同步操作。
3.服务器运行ID
服务器运行ID主要是解决从服务器断线重连后,复制的ID发生改变了,此时我们通过比较服务器ID来避免执行部分重同步操作,避免出现数据不一致问题。
值得注意的是,Redis通过发送复制偏移量还可以解决命令传播丢失的问题,如果主服务器发送的命令传播在网络或者异常情况丢失了,通过对比主从服务器的复制偏移量可以及时发现命令的丢失,主服务器会重新补发这部分丢失的命令。而在2.8版本之前的Redis,即使命令丢失了也不会被发现。
参考内容:Redis设计与实现-黄健宏