Redis 复制(replicate)实现
1. 复制的介绍
Redis
为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,实现Redis
的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。
关于复制的详细配置和如何建立复制,请参考:Redis 复制功能详解 。
- Redis 复制replicate实现
- 复制的介绍
- 复制的实现
- 1 主从关系的建立
- 2 主从网络连接建立
- 3 发送PING命令
- 4 认证权限
- 5 发送端口号
- 6 发送 IP 地址
- 7 发送能力capability
- 8 发送PSYNC命令
- 9 发送输出缓冲区数据
- 10 命令传播
- 部分重同步实现
- 1 心跳机制
- 2 复制积压缓冲区backlog
2. 复制的实现
本文主要剖析:
- 第一次执行复制所进行全量同步的全过程
- 部分重同步的实现
replication.c
文件详细注释:Redis 复制代码注释
2.1 主从关系的建立
复制的建立方法有三种。
- 在
redis.conf
文件中配置slaveof <masterip> <masterport>
选项,然后指定该配置文件启动Redis
生效。 - 在
redis-server
启动命令后加上--slaveof <masterip> <masterport>
启动生效。 - 直接使用
slaveof <masterip> <masterport>
命令在从节点执行生效。
无论是通过哪一种方式来建立主从复制,都是从节点来执行slaveof
命令,那么从节点执行了这个命令到底做了什么,我们上源码:
// SLAVEOF host port命令实现
void slaveofCommand(client *c) {
// 如果当前处于集群模式,不能进行复制操作
if (server.cluster_enabled) {
addReplyError(c,"SLAVEOF not allowed in cluster mode.");
return;
}
// SLAVEOF NO ONE命令使得这个从节点关闭复制功能,并从从节点转变回主节点,原来同步所得的数据集不会被丢弃。
if (!strcasecmp(c->argv[1]->ptr,"no") &&
!strcasecmp(c->argv[2]->ptr,"one")) {
// 如果保存了主节点IP
if (server.masterhost) {
// 取消复制操作,设置服务器为主服务器
replicationUnsetMaster();
// 获取client的每种信息,并以sds形式返回,并打印到日志中
sds client = catClientInfoString(sdsempty(),c);
serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')",
client);
sdsfree(client);
}
// SLAVEOF host port
} else {
long port;
// 获取端口号
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
return;
// 如果已存在从属于masterhost主节点且命令参数指定的主节点和masterhost相等,端口也相等,直接返回
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
&& server.masterport == port) {
serverLog(LL_NOTICE,"SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.");
addReplySds(c,sdsnew("+OK Already connected to specified master\r\n"));
return;
}
// 第一次执行设置端口和ip,或者是重新设置端口和IP
// 设置服务器复制操作的主节点IP和端口
replicationSetMaster(c->argv[1]->ptr, port);
// 获取client的每种信息,并以sds形式返回,并打印到日志中
sds client = catClientInfoString(sdsempty(),c);
serverLog(LL_NOTICE,"SLAVE OF %s:%d enabled (user request from '%s')",
server.masterhost, server.masterport, client);
sdsfree(client);
}
// 回复ok
addReply(c,shared.ok);
}
当从节点的client执行SLAVEOF
命令后,该命令会被构建成Redis
协议格式,发送给从节点服务器,然后节点服务器会调用slaveofCommand()
函数执行该命令。
具体的命令接受和回复请参考:Redis 网络连接库剖析
而SLAVEOF
命令做的操作并不多,主要以下三步:
- 判断当前环境是否在集群模式下,因为集群模式下不行执行该命令。
- 是否执行的是
SLAVEOF NO ONE
命令,该命令会断开主从的关系,设置当前节点为主节点服务器。 - 设置从节点所属主节点的
IP
和port
。调用了replicationSetMaster()
函数。
SLAVEOF
命令能做的只有这么多,我们来具体看下replicationSetMaster()
函数的代码,看看它做了哪些与复制相关的操作。
// 设置复制操作的主节点IP和端口
void replicationSetMaster(char *ip, int port) {
// 按需清除原来的主节点信息
sdsfree(server.masterhost);
// 设置ip和端口
server.masterhost = sdsnew(ip);
server.masterport = port;
// 如果有其他的主节点,在释放
// 例如服务器1是服务器2的主节点,现在服务器2要同步服务器3,服务器3要成为服务器2的主节点,因此要释放服务器1
if (server.master) freeClient(server.master);
// 解除所有客户端的阻塞状态
disconnectAllBlockedClients(); /* Clients blocked in master, now slave. */
// 关闭所有从节点服务器的连接,强制从节点服务器进行重新同步操作
disconnectSlaves(); /* Force our slaves to resync with us as well. */
// 释放主节点结构的缓存,不会执行部分重同步PSYNC
replicationDiscardCachedMaster(); /* Don't try a PSYNC. */
// 释放复制积压缓冲区
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
// 取消执行复制操作
cancelReplicationHandshake();
// 设置复制必须重新连接主节点的状态
server.repl_state = REPL_STATE_CONNECT;
// 初始化复制的偏移量
server.master_repl_offset = 0;
// 清零连接断开的时长
server.repl_down_since = 0;
}
由代码知,replicationSetMaster()
函数执行操作的也很简单,总结为两步:
- 清理之前所属的主节点的信息。
- 设置新的主节点
IP
和port
等。
因为,当前从节点有可能之前从属于另外的一个主节点服务器,因此要清理所有关于之前主节点的缓存、关闭旧的连接等等。然后设置该从节点的新主节点,设置了IP
和port
,还设置了以下状态:
// 设置复制必须重新连接主节点的状态
server.repl_state = REPL_STATE_CONNECT;
// 初始化全局复制的偏移量
server.master_repl_offset = 0;
然后,就没有然后了,然后就会执行复制操作吗?这也没有什么关于复制的操作执行了,那么复制操作是怎么开始的呢?
2.2 主从网络连接建立
slaveof
命令是一个异步命令,执行命令时,从节点保存主节点的信息,确立主从关系后就会立即返回,后续的复制流程在节点内部异步执行。那么,如何触发复制的执行呢?
周期性执行的函数:replicationCron()
函数,该函数被服务器的时间事件的回调函数serverCron()
所调用,而serverCron()
函数在Redis
服务器初始化时,被设置为时间事件