概念
旧版本主从复制
新版本主从复制
部分同步的实现原理
01
—
概念
在redis中,用户可以通过slaveof命令让一台服务器去复制另一台服务器的数据,发起复制的叫从服务器,被复制的交主服务器,进行数据复制的主从服务器将会保持数据的一致性;

02
—
旧版本主从复制
两步:数据同步+命令传播
数据同步
目的:将从服务器的数据库状态更新至主服务器的所处的数据库状态;
当客户端向从服务器发送slaveof命令后,要求从服务器复制主服务器时,会经历四步完成数据同步:
从服务器向主服务器发送sync命令
收到sync命令的主服务器执行bgsave命令,在后台生成一个rdb文件,并使用一个缓冲区记录从现在开始执行的所有写命令
当主服务器的bgsave命令执行完毕后,会通过主从服务器之间的socket连接,将rdb文件发送给从服务器,从服务器接收并载入rdb文件完成数据库状态的同步
最后,主服务器将记录在缓冲区里的写命令发送给从服务器,从服务器接收并执行这些写命令,从而完成最终的数据同步 (完整过程如下图所示)

命令传播
目的:在主服务器数据库状态被修改,导致从服务器的数据库状态和主服务器不一致时,让主从服务器数据库重新回到一致
举个例子:假如一个主服务器和一个从服务器刚刚完成了数据同步,它们的数据库都保存了k1-k4四个键,如下图所示:

此时客户端发起了del请求,将k1删除,此时主从服务器的数据库将处于不一致的状态,如下图所示:

现在由于客户端将主服务器的k1删除,导致主从数据库状态的不一致,那怎么办?这很容易想出来吧各位同学,让主服务器将del命令发送给从服务器就ok啦!如下图所示:

主从复制完整过程如下表格所表示:
| 时间 | 主服务器 | 从服务器 |
| T0 | 启动 | 启动 |
| T1 | 执行set k1 v1 | |
| T2 | 执行set k2 v2 | |
| T3 | 执行set k3 v3 | |
| T4 | 向主服务器发送sync | |
| T5 | 收到sync,执行bgsave命令生成rdb文件,并使用缓冲区记录从当前时间开始的写命令 | |
| T6 | 执行set k4 v4 并将命令保存到缓冲区 | |
| T7 | 执行set k5 v5 并将命令保存到缓冲区 | |
| T8 | bgsave执行完毕,向从服务器发送rdb文件 | |
| T9 | 接收rdb文件,获得k1,k2,k3三个键 | |
| T10 | 向从服务器发送缓冲区里的k4和k5写命令 | |
| T11 | 接收到set k4 v4和setkey5 v5两个命令 | |
| T12 | 同步完成 | 同步完成,数据为k1到k5 |
旧版本复制的缺点
从服务器如果断线重连时,会同步主服务器的全量数据,而不是同步断开过程中产生的主从数据差
03
—
新版本主从复制
为了解决旧版复制功能在处理断线重连时的低效问题,redis从2.8版本开始,使用psync代替sync来执行主从复制的操作,psync有两种模式,一个是完整同步,另一个是部分同步;
完整同步
用于处理初次同步,和旧版的sync一个意思,也是通过主服务器发送rdb文件以及缓冲区里的写命令来完成同步;
部分同步:
用于处理从服务器断线重连的情况,主服务器可以将从服务器断开连接期间的写命令发送给从服务器,这样就完成部分同步,而不需要像老版本那样同步全量数据;
使用psync命令进行断线重连后的部分同步过程如下表格:
| 时间 | 主服务器 | 从服务器 |
| T0 | 主从同步完成 | 主从同步完成 |
| T1 | 执行set k1 v1 | 接收主发来的set命令并执行 |
| T2 | 执行set k2 v2 | 接收主发来的set命令并执行 |
| T3 | 执行set k3 v3 | 接收主发来的set命令并执行 |
| T4 | 主从连接断开 | 主从连接断开 |
| T5 | 执行set k4 v4 | 断线中,尝试连接主服务器 |
| T6 | 执行set k5 v5 | 断线中,尝试连接主服务器 |
| T7 | 主从连接成功 | 主从连接成功 |
| T8 | 发送psync命令 | |
| T9 | 向从服务器返回+continue恢复,表示执行部分同步 | |
| T10 | 接收continue回复,准备部分同步 | |
| T11 | 向从服务器发送set k4和set k5 | |
| T12 | 接收到主服务器发送的set k4和set k5并执行 | |
| T13 | 同步完成 | 同步完成 |
04
—
部分同步的实现
部分同步由三个部分组成
主服务器的复制偏移量和从服务器的复制偏移量
主服务器的复制缓冲区
服务器的运行id(runid)
复制偏移量
执行复制的双方都会维护一个复制偏移量
主服务器每次向从服务器发送N个字节的数据时,会将自己的复制偏移量加N
从服务器接收到主服务器发送过来的N个字节数据后,也会将自己的复制偏移量加N
如下图所示,主从偏移量都是10086

这时,主服务器向三个从服务器发送了33字节的数据,那么主服务器的偏移量会变成10086+33=10119,而从服务器在收到数据后也会将偏移量设置为10119,如下图所示:

考虑下面的场景,在主服务器发送33字节给从服务器时,主从连接突然断开,如下图所示:

那么在从1与主服务器重连之后,数据该如何同步呢?按照我们的猜想,肯定是根据主从的复制偏移量来进行部分同步,没错,你猜对了😘,不过还与复制缓冲区有关(毕竟至少要告诉主服务器从offset开始,发送多少个字节数据给从服务器吧)
复制缓冲区
由主服务器维护的一个固定长度的先进先出(FIFO)队列,默认大小为1MB
当主服务器发送写命令给从服务器时,同时也会将写命令入队到复制缓冲区中,如下图所示:

因此,主服务器的复制缓冲区内保存了最近发送的写命令,并且会记录每个字节对应的偏移量,如下表格所示:

当从服务器重连上主服务器后,从服务器会通过psync命令将自己的offset发送给主服务器,主服务器会根据这个偏移量来决定做何种操作:
如果offset之后的数据仍然存在于复制缓冲区,那么执行部分同步操作
如果offset之后的数据不存在,那么执行完整同步操作
回到上面主从断开连接的图,当主从服务器恢复连接后
从服务器通过psync命令告诉主服务器自己的offset是10086
主服务器收到psync命令和offset之后,检查了一下复制缓冲区10086偏移量后面的数据还在不在,结果发现这些数据还在,就给从服务器返回了一个continue表示要进行部分同步
接着主服务器会将复制缓冲区里10086偏移量后面的数据(10087至10119)都发送给从服务器
从服务器只要接收到这33字节的数据,就可以回到与主服务器一致的状态,如下图所示:

服务器运行id(用来判断部分同步还是完整同步)
每个redis服务器无论主从都有自己的运行id,当从服务器首次同步的时候,主服务器会将运行id传给从服务器,从服务器将其保存,当从服务器断线重连时,从服务器会将之前的主服务器运行id和当前的比较一下:
如果相同,说明断开和重连后连接的是同一台主服务器,那么进行部分同步就可以了
相反,主服务器已经改变,那么进行完整同步




