Redis(二十一):复制
上图可以看到,从服务器的 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 个随机的十六进制字符组成
评论