多机数据库的实现
十四、复制
用户可以通过执行SLAVEOF命令以及设置slaveof选项,让一个服务器去复制另一个服务器。
14.1 旧版复制功能
Redis的复制功能分为同步与命令传播两个操作;
1、同步
客户端向从服务器发送SLAVEOF命令,则从服务器需要先执行同步操作,,从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成,以下为SYNC的执行步骤:
1、从向主发送SYNC命令;
2、主收到后执行BGSAVE命令,后台生成一个RDB文件,使用缓冲区记录从现在开始执行的所有写命令;
3、BGSAVE执行完,主发送RDB文件给从,从接收并载入RDB;
4、主发送缓冲区保存的所有写命令,藏执行这些写命令;
2、命令传播
执行完同步后,客户端对主服务器的所有后续操作,主服务器都会向从发送执行的命令来保证后续的一致性;
14.2 旧版复制缺陷
复制分为初次复制和断线后重复制,对于初次复制,旧版实现可以很好完成,但对于断线后重复制,会出现下面的例子:
问题即是为了让从服务器不足一部分缺失的数据,却得让主从服务器重新执行一次SYNC命令,这样的效率分成低;
14.3 新版复制功能
新版使用PSYNC命令替换SYNC命令,而PSYNC命令具有完整同步和部分同步两种模式;部分同步就解决了旧版的低效问题;
1、部分重同步实现
- 主服务器的复制偏移量和从服务器的复制偏移量;
- 主服务器的复制积压缓冲区;
- 服务器运行的ID;
复制偏移量
主从都分别维护一个复制偏移量;主每次发送N个字节数据,自己的复制偏移量就+N;从收到主的N个字节数据,自己的复制偏移量也+N。通过复制偏移量可以知道主从的一致性;
复制积压缓冲区
由主服务器维护的一个固定长度先进先出队列,默认为1MB(固定长度先进先出队列执行时,当入队元素数量>队列长度时,最先入队元素会被弹出,新元素放入);
主进行命令传播时,不仅发送命令给从,还会将写命令入队到复制积压缓冲区。
当从重新连上主时,从会通过PSYNC命令将自己的复制偏移量offset发给主,主根据offset来决定执行何种同步:
若offset偏移后的数据存在于复制积压缓冲区,则进行部分同步;不在则进行完整重同步;
服务器运行ID
每个Redis服务器都有自己的运行ID,从对主进行初次复制时,主会发送自己的ID给从,从会保存下来,之后断线重连时,从会发送保存的ID给主,若ID相同,则主可以尝试执行部分重同步,不同则需要执行完整重同步;
14.4 PSYNC命令实现
14.5 复制实现
通过命令SLAVEOF <master_ip> <master_port>;
1、步骤1:设置主服务器的地址和端口
struct redisServer {
//....
//主服务器地址
char *masterhost;
//主服务器端口
int masterport;
//...
};
2、步骤2:建立套接字
3、步骤3:发送PING命令
4、步骤4:身份验证
5、步骤5:发送端口信息
步骤四之后,从将执行REPLCONF listening-port ,向主发送从的监听端口号。主会将端口号记录在从对应的客户端状态的slave_listening_port属性中:
typedef struct redisClient {
//...
//从的监听端口号
int slave_listening_port;
//...
}redisClient;
6、步骤6:同步
7、步骤7:命令传播
14.6 心跳检测
命令传播阶段,从会默认每秒一次向主发送命令:REPLCONF ACK <replication_offset>,其中replication_offset就是从服务器当前的复制偏移量;
该命令作用:
1、检测主从服务器的网络连接状态
若主服务器发送INFO replication命令,在列出的从服务器列表的lag一栏中,可看到相应从最后一次向主发送REPLCONF ACK 命令距离现在过了多少秒:(lag一般在0或1之间跳动,超过1,说明主从连接出现故障)
127.0.0.1:6379> INFO replication
role:master
....
slave0:ip=.....,lag=0 # 刚刚发送过REPLCONF ACK 命令
slave1:ip=.....,lag=15 # 15秒前发送过REPLCONF ACK 命令
2、辅助实现min-slaves配置选项
Redis的min-slaves-to-write和min-slaves-max-lag可放置主服务器在不安全情况下执行写命令;
举个例子:
min-slaves-to-write 3
min-slaves-max-lag 10
# 从服务器数量<3个或三个从服务器的延迟(lag)都>=10s,则主服务器拒绝执行写命令
3、检测命令丢失
若由于网络故障,主给从发送的命令丢失,则从给主发送REPLCONF ACK 时,主会发现replication_offset不一致,所以会去找复制积压缓冲区里对应的指令进行命令的补发
注意:补发缺失数据操作在主从没有断线情况下执行;而部分重同步时主从断线并重连后执行。
十五、Sentinel
由一个或多个Sentinel(哨兵、哨岗)组成的Sentinel系统可监视多台主从服务器,主服务器下线时,可进行故障转移,将从服务器升级为主服务器。
15.1 启动并初始化Sentinel
# 启动一个Sentinel可以使用的命令
$ redis-sentinel /path..../sentinel.conf
或 $ redis-server /path.../sentinel.conf --sentinel
启动一个Sentinel时执行的步骤:
1、初始化服务器
Sentinel本质是一个特殊模式下的Redis服务器,所以初始化步骤与13章类似。不过略有不同,主要功能使用情况如下:
2、使用Sentinel专用代码
例如使用sentinel.c/sentinelcmds作为服务器的命令表,使用sentinel.c/REDIS_SENTINEL_PORT常量值作为服务器端口;
3、初始化Sentinel状态
保存服务器中所有和Sentinel功能相关的状态:
struct sentinelState {
//当前纪元,用于实现故障转移
uint64_t current_epoch;
//保存所有被这个sentinel监视的主服务器
//字典的键为主服务器名,值为指向sentinelRedisInstance结构的指针
dict *masters;
//是否进入TILT模式
i