复制
复制的难点在同步阶段,让我们先了解同步的实现:
1.同步的实现
概念:让从服务器的状态更新到与主服务器一致。
旧版的同步实现
旧版的同步操作是通过SYNC命令来完成的,执行步骤为:
- 从服务器向主服务器发送SYNC命令。
- 主服务器收到SYNC命令之后,主服务器会执行BGSAVE命令,然后在后台生成一个RDB持久化文件,并用一个重写缓存区记录从现在开始的所有写命令。
- 当主服务器的BGSAVE命令执行完之后,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器会接收并载入这个RDB文件,将自己的数据库更新至主服务器执行BGSAVE命令时的数据库状态,将自己的数据库更新至主服务器执行BGSAVE命令这一刻的状态。
- 主服务把重写缓存器的写命令发送给从服务器,让从服务器恢复到主服务的最新状态。
缺陷:同步分成两种情况,初次复制和断线后复制,如果是初次复制,用RDB文件复制的方法没有什么问题,但如果是断线复制的情况,主服务器和从服务器的数据相差无几,如果也执行RDB文件恢复未免代价也太大了!
新版的同步实现
为了解决断线复制方法的低效问题,Redis2.8用PSYNC命令代替了SYNC命令。
PSYNC有完整重同步和部分重同步的模式:
- 完整重同步解决的是初次复制的情况,用RDB持久化文件实现。
- 部分重同步解决的是断线后重复的情况,主服务器只需要把断线阶段的写命令发送给从服务器即可。
首先了解几个概念:
- 复制偏移量:主从服务器都会维护复制偏移量,记录当前的数据长度。
- 复制挤压缓存区:固定长度的先进先出的队列,记录主服务器的最新写命令。
- 服务器运行id:相当服务器的编号,主服务器在初次复制时会把自己的id发送给从服务器。
部分重同步的实现:
![](/qrcode.jpg)
首先从服务器发送自己的复制偏移量给主服务,主服务器把这个复制偏移量与自身的复制偏移量对比,然后查看自己的复制挤压缓存中是否还保留着这部分数据,如果保留着,那么只需要把这个部分的数据发送给从服务器就行,否则执行完全重同步。
2.复制的实现
步骤一:设置主服务器的地址和端口
当客户端向从服务器发送以下命令时:
SLAVEOF 127.0.0.1 6379
从服务器会把这个主服务器的ip和端口保存起来:
struct redisServer{
//主服务器地址
char *masterhost;
//主服务器端口
int masterport;
}
步骤二:建立套接字连接
从服务器根据自己设置的IP地址和端口,来创建连接主服务器的套接字。
步骤三:发送ping命令
创建完套接字之后,从服务器发送一个ping命令给主服务器,为了检查主服务器是否能正常处理命令 和 检查他的读写状态是否正常。
发送命令之后会遇到三种情况:
- 如果超过时限,那么表示主从服务器连接状态不稳定,这个时候从服务器断开并重新创建连向主服务器的套接字。
- 如果主服务器向从服务器返回一个错误,表示从服务器暂时无法处理从服务器的请求,这种情况从服务器断开并重新创建连向主服务器的套接字。
- 如果从服务器收到PONG回复,表示主服务器连接正常,可以进行下一个步骤。
步骤四:身份验证
从服务器收到PONG回复之后,下一步就是决定是否进行身份验证。
负责验证身份的从服务器是masterauth选项,主服务器是requirepass选项。身份验证有三种情况:
- 主服务器没有设置requirepass选项,并且从服务器也没有设置masterauth选项,那么不需要验证。
- 主服务器设置requirepass选项,并且从服务器设置masterauth选项,如果从服务器发送的密码与主服务器设置的相同,那么通过。
- 如果主服务器设置了requirepass,从服务器却没有设置masterauth选项,那么主服务器将返回一个NOAUTH错误。
- 如果主服务器没有设置requirepass选项,从服务器却设置了masterauth选项,那么主服务器将返回一个no password is set 错误。
步骤五:发送端口信息
从服务器向主服务器发送从服务器的监听端口号。
typedef struct redisClient{
//从服务器的监听端口号
int slave_listening_port;
}
步骤六:同步
从服务器将向主服务器发送PSYNC命令执行同步操作,把自己的数据库更新到主服务器数据库当前所处的状态。
步骤六:命令传播
当完成了同步之后,主从服务器就会进入命令传播阶段,这时主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接收并执行主服务器发来的写命令,就可以保证主从服务器的一致性。
步骤七:心跳检测问题
在命令传播阶段,从服务器还会默认以每秒一次的频率,向主服务器发送心跳命令,来检测主从服务器的网络连接状态。