复制
在Redis中,我们可以通过执行SLAVEOF命令或者设置slaveof选项(如果有主服务器有密码,还需要设置masterauth选项,最好把日志功能打开,即设置logfile选项,失败的话,可以从日志中看),让一个服务器去复制另一个服务器,被复制的服务器称为主服务器,而对主服务器进行复制的服务器称为从服务器
举个栗子
slaveof 127.0.0.1 6379
执行上面的命令后,本服务器就会成为127.0.0.1 6379的从服务器
进行复制中的主从服务器双方的数据库将保存相同的数据,这种现象称为主从一致
旧版复制功能的实现
Redis的复制功能分为同步(sync/fsync)和命令传播(command propagate)两个操作。
- 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态(从服务器执行)
- 命令传播操作则用于在主服务器的数据库状态被修改后(前面已经完成同步操作),导致发生了主从不一致的现象时,让主从服务器的数据库重新回到一致状态。(主服务器执行)
同步
当客户端向从服务器发送SLAVEOF命令,要求从服务器复制主服务器时,从服务器首先会需要去执行同步操作,将自己的状态更新成与主服务器状态一致。(此时从服务器只能读而不可以写了)
从服务器对主服务器的同步操作,需要通过向主服务器发送SYNC命令来完成,具体的执行步骤如下所示
-
从服务器向主服务器发送SYNC命令
-
主服务器接收到从服务器发送的SYNC命令,此时会进行一个BGSAVE命令,在后台生成一个RDB文件,并且会使用一个缓冲区记录从现在开始执行的所有写命令
-
当主服务器完成BGSAVE命令后,主服务器会将生成的RDB文件发送给从服务器,从服务器会接受并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态(注意此时从服务器里面原有的数据就会消失不见)
-
主服务完成BGSAVE命令后,还会将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,再将自己的数据库状态更新至主服务器数据库当前的状态(避免了执行BGSAVE时数据的丢失)

上图可以看到,从服务器的onlymehave字符串对象不见了

命令传播
同步操作执行完后,从服务器的状态跟主服务器一致了,但这种一致并不是永远保持的,每当主服务器执行客户端发送的写命令时,主服务器的数据库就可能被修改,但从服务器并没有修改,主从又不一致了。
为了让主从再回到一致状态,主服务器就需要对从服务器执行命令传播操作
命令传播操作即:主服务器将自己新执行的写命令,发送给从服务器,从服务器执行发送过来的命令,这样主从状态又一致了。
旧版复制功能的缺陷
旧版复制功能的复制可以分为下面两种情况
- 初次复制:从服务器在复制以前没有复制过任何主服务器,或者从服务器要复制新的主服务器
- 断线后重复制:从服务器已经完成同步,处于命令传播状态,在接受主服务器新执行的命令,但此时从服务器由于网络原因与主服务器断开了连接,需要进行重新连接,连接后要进行重新复制
对于第一种情况,旧版是做的不错的。
关键在于第二种情况,断线后重新复制是很浪费资源的(因为重新连接后无法判断此时主从状态是否一致,所以只能重新同步,完成一致),比如说,你主从服务器已经完成同步,已经有1万个键同步好了,但此时你从服务器宕机了,重启后,又去进行同步,又要去复制1万个键,包括宕机时主服务器插入的新键,但实际上,重连的从服务器只需要复制宕机时主服务器插入的新键即可。
总结来说,就是Sync命令会十分耗费资源,因为每次执行Sync,主从服务器要进行下列的步骤
- 主服务器要执行BGSAVE命令生成RDB文件,耗费主服务器的CPU、内存和磁盘IO资源
- 主服务器执行完BGSAVE之后,还要发送RDB文件给从服务器,耗费网络资源
- 从服务器接受主服务器发送的RDB文件,然后进行载入RDB文件,此时耗费CPU去读取,而且载入RDB文件时,是会发生阻塞的,从服务器会停止对外提供读服务。
所以,要在真正有必要的时候,才去执行Sync命令
新版复制功能的实现
新版复制功能其实是为了解决旧版的断线重复制的问题(为了解决主从状态是否一致问题),从2.8版本开始,使用Psync命令代替了Sync命令(即从服务器不再发送Sync命令,取而代之的是Psync命令)
Psync命令具有完整重同步和部分重同步两种模式
- 完整重同步是用于初次复制,也就是第一次同步的情况,完整重同步的执行步骤和Sync基本是一样的,同样主服务器进行BGSAVE,然后发送RDB,缓冲池记录新的写命令,从服务器接受RDB,进行载入,载入后接受主服务器发送缓冲池新的写命令,然后执行新的写命令,完成整个同步操作
- 部分重同步则是针对于断线后重复制情况的,当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将从服务器连接断开期间执行的写命令发送给从服务器,而不是重新复制,从服务器接收并去执行这些命令,这也就解决了全部重新复制的问题
部分重同步的过程如下图所示
部分重同步的实现
完整重同步的实现跟Sync差不多,这里就不重复了,下面说一下部分重同步的实现
部分重同步功能由以下三个部分构成
- 主服务器的复制偏移量和从服务器的复制偏移量
- 主服务器的复制积压缓冲区
- 服务器的运行ID
复制偏移量
执行复制的双方,也就是主从服务器,都要分别去维护一个复制偏移量(初始为0)
- 主服务器每次向从服务器发送数据时,要统计发送了多少字节,然后将自己的复制偏移量加上N
- 从服务器每次从主服务器接受数据时,也要统计接受了多少字节,然后将自己的复制偏移量加上N
这样就可以通过比对主从的复制偏移量,就可以判断主从状态是否一致了
- 如果主从状态一致,那么主从服务器两者的偏移量总是相同的
- 如果两者的偏移量不相同,说明了主从状态并不一致
复制积压缓冲区
通过复制偏移量,我们可以判断主从服务器状态是否一致了,但是真正要去执行完整重同步还是部分重同步,就要由复制积压缓冲区去决定了
复制积压缓冲区是由主服务器维护的一个固定长度、先进先出的队列,默认大小为1MB
拓展固定长度队列
固定长度队列与普通队列不太一样,普通队列是可以动态改变自己长度的,而固定长度队列则不可以动态改变,而是固定的,正因为是固定的,所以一旦长度达到最大值,此时如果再往里面添加元素,就会弹出最先进入的元素,然后再添加新的元素。
回到复制积压缓冲区
当主服务器进行命令传播时,不仅仅会将写的命令发送给所有服务器,还会将写命令入队到复制积压缓冲区里面。如下图所示

在复制积压缓冲区会保存着一部分最近传播的写命令,并且会为队列中每一个字节记录其相应的复制偏移量
举个栗子
加入主服务器这里执行了一个新的写命令
set a 12
那么里面这条写命令在复制积压缓冲区里面大概如下图所示

当然,图中并不是很严谨,可能存在一些分隔符,这些分隔符也是要占偏移量的。
此时就有问题了?假如由于复制积压缓冲区底层固定长度队列满了,出去了某一个字符,不就会造成命令不完整了吗?复制不是肯定会出问题吗?
这个问题是不会出现的,因为从服务器在通过发送Psync命令时,会将自己的复制偏移量(offset)发给主服务器,主服务会根据这个复制偏移量来决定从服务器执行何种同步操作。
- 如果offset偏移量之后的数据,也就是从offset+1后的数据仍然存在于复制积压缓冲区里面,那么主服务器就会对从服务器执行部分重同步操作
- 如果offset偏移量之后的数据,只要有一个不存在,即只要判断offset+1的数据是否存在,如果不存在,就会进行完整重同步
过程如下
- 从服务器断线后,重新连接主服务器后,发送Psync命令,并发送自己的复制偏移量
- 主服务器收到从服务器发来的Psync命令以及偏移量之后,主服务器去检查偏移量之后的数据是否存在于复制积压缓冲区里面,如果存在,主服务器就会向从服务器发送+continue回复,表示数据同步将会以部分重同步方式去实现
- 接着主服务器将复制挤压缓冲区偏移量之后的所有数据都发送给从服务器
- 从服务器只需要接受这部分的写命令数据,然后执行,就可以回到与主服务器一致的状态

举个栗子,比如从服务器发送的位置偏移量为10085,只要10086偏移量存在于队列中,那么后面的命令部分肯定都是存在的,因为会优先弹出10085偏移量的字节,如果10086偏移量不存在于队列中,那么命令肯定是不完整的,那么就会执行完整重同步
调整复制积压缓冲区的大小
Redis为复制挤压缓冲区分配的大小默认为1MB,但这是可以改变的,根据实际需求去改变,一般通过两个两个指标去评估
- second:从服务器断线后,重新连接上服务器需要的平均时间或者最长时间
- write_per_size_per_second:主服务器每秒产生的写命令数据量
缓冲区的大小一般设为这两个数值乘积,不过为了安全起见,也可以设为2倍
至于复制积压缓冲区大小的修改方法,参考配置文件的repl-backlog-size选项的说明

服务器运行ID
前两步已经完成了,下面到最后一步,服务器运行ID
- 每个Redis服务器,不论主服务器还是从服务器,都会有自己的运行ID
- 运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成
除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行ID(RunId),用来给主服务器区分从服务器是不是断线的服务器,还是另一个新的从服务器。
当从服务器对主服务进行初次同步的时候,主服务器会将自己的运行ID发给从服务器,而从服务器会把这个保存起来。
当从服务器重新连接一个主服务器时,从服务器会向当前连接的主服务器发送保存的服务器的运行ID
- 主服务器去判断从服务器发送的运行ID是否与自己的一样,如果一样,证明是重新连接的,根据情况去判断是否需要执行部分重同步
- 如果不一样,主服务器就知道这个是新的从服务器,执行的是完整重同步
本文解析Redis旧版复制的同步与命令传播,阐述其在断线后重复制的效率问题,介绍新版Psync与复制偏移量、积压缓冲区等机制如何实现部分重同步,降低资源消耗。
946

被折叠的 条评论
为什么被折叠?



