数据同步
当用户将一个服务器设置为从服务器,让它去复制另一个服务器的时候,主从服务器需要通过数据同步机制来让两个服务器的数据库状态保持一致。
这一节将对 Redis 主从服务器的数据同步机制进行介绍,理解同步机制的运作原理是阅读本章后续内容的基础。
完整同步
当一个 Redis 服务器接收到 REPLICAOF
命令,开始对另一个服务器进行复制的时候,主从服务器会执行以下操作:
主服务器执行
BGSAVE
命令,生成一个 RDB 文件,并使用缓冲区储存起在BGSAVE
命令之后执行的所有写命令。在 RDB 文件创建完毕之后,主服务器会通过套接字,将 RDB 文件传送给从服务器。
从服务器在接收完主服务器传送过来的 RDB 文件之后,就会载入这个 RDB 文件,从而获得主服务器在执行
BGSAVE
命令时的所有数据。当从服务器完成 RDB 文件载入操作,并开始上线接受命令请求时,主服务器就会把之前储存在缓存区里面的所有写命令发送给从服务器执行。
因为主服务器储存的写命令都是在执行 BGSAVE
命令之后执行的,所以当从服务器载入完 RDB 文件,并执行完主服务器储存在缓冲区里面的所有写命令之后,主从服务器两者包含的数据库数据将完全相同。
这个通过创建、传送并载入 RDB 文件来达成数据一致的步骤,我们称之为完整同步操作。每个从服务器在刚开始进行复制的时候,都需要与主服务器进行一次完整同步。
注解
在进行数据同步时重用 RDB 文件
为了提高数据同步操作的执行效率,如果主服务器在接收到 REPLICAOF
命令之前已经完成了一次 RDB 创建操作,并且它的数据库在创建 RDB 文件之后没有发生过任何变化,那么主服务器将直接向从服务器发送已有的 RDB 文件,以此来避免无谓的 RDB 文件生成操作。
此外,如果在主服务器创建 RDB 文件期间,有多个从服务器向主服务器发送数据同步请求,那么主服务器将把发送请求的从服务器全部放入到队列里面,等到 RDB 文件创建完毕之后,再把它发送给队列里面的所有从服务器,以此来复用 RDB 文件并避免多余的 RDB 文件创建操作。
在线更新
主从服务器在执行完完整同步操作之后,它们的数据就达到了一致状态,但这种一致并不是永久的:每当主服务器执行了新的写命令之后,它的数据库就会被改变,这时主从服务器的数据一致性就会被破坏。
为了让主从服务器的数据一致性可以保持下去,让它们一直拥有相同的数据,Redis 会对从服务器进行在线更新:
每当主服务器执行完一个写命令之后,它就会将相同的写命令又或者具有相同效果的写命令发送给从服务器执行。
因为完整同步之后的主从服务器在执行最新出现的写命令之前,两者的数据库是完全相同的,而导致两者数据库出现不一致的正是最新被执行的写命令。因此从服务器只要接收并执行主服务器发来的写命令,就可以让自己的数据库重新与主服务器数据库保持一致。
只要从服务器一直与主服务器保持连接,在线更新操作就会不断进行,使得从服务器的数据库可以一直被更新,并与主服务器的数据库保持一致。
注解
异步更新引起的数据不一致
需要注意的是,因为在线更新是异步进行的,所以在主服务器执行完写命令之后,直到从服务器也执行完相同写命令的这段时间里,主从服务器的数据库将出现短暂的不一致,因此要求强一致性的程序可能需要直接读取主服务器而不是读取从服务器。
此外,因为主服务器可能在执行完写命令并向从服务器发送相同写命令的过程中由于故障而下线,所以从服务器在主服务器下线之后可能会丢失主服务器已经执行的一部分写命令,导致从服务器的数据库与下线之前的主服务器数据库处于不一致状态。
因为在线更新的异步本质,Redis 的复制功能是无法杜绝不一致的。不过本章之后会介绍一种方法,它可以尽量减少不一致出现的可能性。
部分同步
当故障下线的从服务器重新上线时,主从服务器的数据通常已经不再一致,因此它们必须重新进行同步,让两者的数据库再次回到一致状态。
在 Redis 2.8 版本以前,重同步操作是通过直接进行完整同步来实现的,但是,这种重同步方法在从服务器只是短暂下线的情况下是非常浪费资源的:主从服务器的数据库在连接断开之前一直都是相同的,造成数据不一致的原因可能仅仅是因为主服务器比从服务器多执行了几个写命令,而为了补上这小部分写命令所产生的数据,却要大费周章地重新进行一次完整同步,这毫无疑问是非常低效的。
为了解决这个问题,Redis 从 2.8 版本开始使用新的重同步功能去代替原来的重同步功能:
当一个 Redis 服务器成为另一个服务器的主服务器时,它会把每个被执行的写命令都记录到一个特定长度的先进先出队列里面。
当断线的从服务器尝试重新连接主服务器的时候,主服务器将检查从服务器断线期间,被执行的那些写命令是否仍然保存在队列里面。如果是的话,那么主服务器就会直接把从服务器缺失的那些写命令发送给从服务器执行,从服务器通过执行这些写命令就可以重新与主服务器保持一致,这样就避免了重新进行完整同步的麻烦。
另一方面,如果从服务器缺失的那些写命令已经不存在于队列当中,那么主从服务器将进行一次完整同步。
因为新的重同步功能需要使用先进先出队列来记录主服务器执行过的写命令,所以这个队列的体积越大,它能够记录的写命令就越多,从服务器断线之后能够快速地重新回到一致状态的机会也就越大。Redis 为这个队列设置的默认大小为 1 MB ,用户也可以根据自己的需要,通过配置选项 repl-backlog-size
来修改这个队列的大小。