1.概述
redis在目前生产环境都是集群部署,通过哨兵保证集群的高可用。但凡涉及到集群以及存储方面的需求,无法避免的就是数据复制问题。这篇文章主要说一下redis复制原理,以及redis复制需要注意的问题,还有就是业务方应该避免的坑。
2.核心点
建立主从命令slaveof
slaveof no one: 取消现有的主从关系,使slave变成master
slaveof host port:将当前实例变成指定节点的从节点。
主从命令slaveof实现(定时任务驱动)
通过定时任务和主节点建立连接并进行复制前逻辑
定时任务流程:
(1)任务发现存在新的主节点,建立连接
(2)发送ping
(3)鉴权 如果主节点有requirepass。那么从节点需要配置masterauth
(4)同步全量数据
(5)持续发送写命令到从节点
内部数据同步使用psync命令
psync命令响应:
- +FULLRESYNC:全量复制
- +CONTINUE 部分复制
- +ERR 版本太低
全量复制
从节点发送psync-1 进行全量复制
部分复制
通过offset 以及 运行id来实现部分复制,如果主节点的复制积压缓冲区(back_log)存在数据的话。直接返回给从节点可进行部分复制。
这里说一下部分复制依赖的是slave的offset,以及主节点的back_log。主节点的back_log是一个1M大小的环形链表。主节点每次处理命令的时候都会将命令写入back_log。
心跳探活
主节点通过心跳可以判断节点是否存活。
从节点每1s通过replconf ack offset命令上报当前复制的偏移量。
3.原理分析
Redis的slave通过server.repl_state状态来区分当前的复制状态。整个复制流程就是状态转变的过程。
#define REDIS_REPL_NONE 0 /* No active replication */ 没有进行复制
#define REDIS_REPL_CONNECT 1 /* Must connect to master */ 连接
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */ 建立连接
#define REDIS_REPL_RECEIVE_PONG 3 /* Wait for PING reply */ 等待ping
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */ 传输 rdb
#define REDIS_REPL_CONNECTED 5 /* Connected to master */ 复制完成,进行增量同步
Redis的master通过对应slave客户端的replstate来记录当前客户端的状态
#define REDIS_REPL_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */ 客户等待执行rdb
#define REDIS_REPL_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */ 等待rdb执行完成
#define REDIS_REPL_SEND_BULK 8 /* Sending RDB file to slave. */ 接收rdb中
#define REDIS_REPL_ONLINE 9 /* RDB file transmitted, sending just updates. */ 在线 命令传播
接下来我们对源码进行分析。
slaveofCommand方法
该方法为slaveof命令的命令处理方法。我们主要看一下slaveof host port逻辑。
long port;
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != REDIS_OK))
return;
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
&& server.masterport == port) {
addReplySds(c,sdsnew("+OK Already connected to specified master\r\n"));
return;
}
replicationSetMaster(c->argv[1]->ptr, port);
如果指定的host、port已经是当前节点的master,直接返回,否则调用replicationSetMaster去设置master
replicationSetMaster方法
void replicationSetMaster(char *ip, int port) {
// 清除原有的主服务器地址(如果有的话)
sdsfree(server.masterhost);
// IP
server.masterhost = sdsnew(ip);
// 端口
server.masterport = port;
if (server.master) freeClient(server.master);
disconnectSlaves(); /* Force our slaves to resync with us as well. */
// 清空可能有的 master 缓存,因为已经不会执行 PSYNC 了
replicationDiscardCachedMaster();
freeReplicationBacklog();
cancelReplicationHandshake();
// 进入连接状态
server.repl_state = REDIS_REPL_CONNECT;
server.master_repl_offset = 0