/* 检测是否连接超时,并尝试重新连接 master */ if (server.masterhost && (server.repl_state == REPL_STATE_CONNECTING || slaveIsInHandshakeState()) && (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout) { serverLog(LL_WARNING,"Timeout connecting to the MASTER..."); cancelReplicationHandshake(1); }
/* 检测是否 I/O 超时,并尝试重新连接 master */ if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER && (time(NULL)-server.repl_transfer_lastio) > server.repl_timeout) { serverLog(LL_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value."); cancelReplicationHandshake(1); }
/* Timed out master when we are an already connected slave? */ if (server.masterhost && server.repl_state == REPL_STATE_CONNECTED && (time(NULL)-server.master->lastinteraction) > server.repl_timeout) { serverLog(LL_WARNING,"MASTER timeout: no data nor PING received..."); freeClient(server.master); }
/* 如果状态是 REPL_STATE_CONNECT:Must connect to master,则连接 master*/ if (server.repl_state == REPL_STATE_CONNECT) { serverLog(LL_NOTICE,"Connecting to MASTER %s:%d", server.masterhost, server.masterport); connectWithMaster(); }
/* 如果 master 支持 REPLCONF 命令,则 Send ACK to master from time to time. * 通知 master 当前处理的 offset */ if (server.masterhost && server.master && !(server.master->flags & CLIENT_PRE_PSYNC)) replicationSendAck();
/* First, send PING according to ping_slave_period. */ if ((replication_cron_loops % server.repl_ping_slave_period) == 0 && listLength(server.slaves)) { /* Note that we don't send the PING if the clients are paused during * a Redis Cluster manual failover: the PING we send will otherwise * alter the replication offsets of master and slave, and will no longer * match the one stored into 'mf_master_offset' state. */ int manual_failover_in_progress = ((server.cluster_enabled && server.cluster->mf_end) || server.failover_end_time) && checkClientPauseTimeoutAndReturnIfPaused();
/* 第二, 发一个 newline 给所有的 slaves,表示处于 pre-synchronization 状态, * 表示 slaves 等待 master 创建 RDB file. * * (级联模式下)还给所有 slave 的 slave 节点发一个 newline, 让他们知道他们的 master 还活着. * This is needed since sub-slaves only receive proxied * data from top-level masters, so there is no explicit pinging in order * to avoid altering the replication offsets. This special out of band * pings (newlines) can be sent, they will have no effect in the offset. * * slave 节点会忽略掉 newline,只是刷新一下 last interaction timer 防止超时 * In this case we ignore the * ping period and refresh the connection once per second since certain * timeouts are set at a few seconds (example: PSYNC response). */ listRewind(server.slaves,&li); while((ln = listNext(&li))) { client *slave = ln->value;
/* 如果命令中的 host/port 被设置为 "NO" "ONE" 时(即:replicaof no one), * 本机将变为 master */ if (!strcasecmp(c->argv[1]->ptr,"no") && !strcasecmp(c->argv[2]->ptr,"one")) { if (server.masterhost) { replicationUnsetMaster(); sds client = catClientInfoString(sdsempty(),c); serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')", client); sdsfree(client); } } else { long port;
if (c->flags & CLIENT_SLAVE) { /* If a client is already a replica they cannot run this command, * because it involves flushing all replicas (including this * client) */ addReplyError(c, "Command is not valid when client is a replica."); return; } // 校验端口号 if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK)) return;
/* 校验需要连接的 master,是不是当前已经连上的机器 */ if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr) && server.masterport == port) { serverLog(LL_NOTICE,"REPLICAOF 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; }
/* 与 master 建立起 non blocking connect 之后,就会触发这个方法 */ voidsyncWithMaster(connection *conn){ char tmpfile[256], *err = NULL; int dfd = -1, maxtries = 5; int psync_result;
······
/* 向 master 发送 ping 命令,发送完就 return 了。 * 注意这里的 handler 还是 syncWithMaster() 自身, * 收到回复(pong)之后,会在下一段代码里处理. */ if (server.repl_state == REPL_STATE_CONNECTING) { serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event."); /* Delete the writable event so that the readable event remains * registered and we can wait for the PONG reply. */ connSetReadHandler(conn, syncWithMaster); connSetWriteHandler(conn, NULL); server.repl_state = REPL_STATE_RECEIVE_PING_REPLY; /* Send the PING, don't check for errors at all, we have the timeout * that will take care about this. */ err = sendCommand(conn,"PING",NULL); if (err) goto write_error; return; }
/* 处理 PONG 命令. */ if (server.repl_state == REPL_STATE_RECEIVE_PING_REPLY) { err = receiveSynchronousResponse(conn); if (err[0] != '+' && strncmp(err,"-NOAUTH",7) != 0 && strncmp(err,"-NOPERM",7) != 0 && strncmp(err,"-ERR operation not permitted",28) != 0) { serverLog(LL_WARNING,"Error reply to PING from master: '%s'",err); sdsfree(err); goto error; } else { serverLog(LL_NOTICE, "Master replied to PING, replication can continue..."); } ······ // ping-pong 完成,表示可以连接上,准备 handshake 交换信息 server.repl_state = REPL_STATE_SEND_HANDSHAKE; }
// handshake:通过账号密码连接 master // 连接成功后,发送自己的信息 if (server.repl_state == REPL_STATE_SEND_HANDSHAKE) { /* AUTH with the master if required. */ if (server.masterauth) { char *args[3] = {"AUTH",NULL,NULL}; size_t lens[3] = {4,0,0}; int argc = 1; if (server.masteruser) { args[argc] = server.masteruser; lens[argc] = strlen(server.masteruser); argc++; } args[argc] = server.masterauth; lens[argc] = sdslen(server.masterauth); argc++; err = sendCommandArgv(conn, argc, args, lens); if (err) goto write_error; }
/* 发送 slave 节点的 port, 以便 Master 的 info replication 命令 * 可以正确的打印 slave 监听的端口 */ { int port; if (server.slave_announce_port) port = server.slave_announce_port; elseif (server.tls_replication && server.tls_port) port = server.tls_port; else port = server.port; sds portstr = sdsfromlonglong(port); err = sendCommand(conn,"REPLCONF", "listening-port",portstr, NULL); sdsfree(portstr); if (err) goto write_error; }
/* 发送 slave ip, , 以便 Master 的 info replication 命令 * 可以正确的打印 slave 的 ip 地址 */ if (server.slave_announce_ip) { err = sendCommand(conn,"REPLCONF", "ip-address",server.slave_announce_ip, NULL); if (err) goto write_error; }
/* 通知 master 自己支持哪些命令(capabilities). * * EOF: supports EOF-style RDB transfer for diskless replication. * PSYNC2: supports PSYNC v2, so understands +CONTINUE <new repl ID>. * * The master will ignore capabilities it does not understand. */ err = sendCommand(conn,"REPLCONF", "capa","eof","capa","psync2",NULL); if (err) goto write_error;
/* Increment stats for failed PSYNCs, but only if the * replid is not "?", as this is used by slaves to force a full * resync on purpose when they are not albe to partially * resync. */ if (master_replid[0] != '?') server.stat_sync_partial_err++; } } ······
/* 全量复制. */ server.stat_sync_full++;
/* 全量同步时,需要 master 生成并发送自己的 rdb 文件, * 所以先把 slave 的状态设置为 SLAVE_STATE_WAIT_BGSAVE_START 等待状态 */ c->replstate = SLAVE_STATE_WAIT_BGSAVE_START; if (server.repl_disable_tcp_nodelay) connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */ c->repldbfd = -1; c->flags |= CLIENT_SLAVE; listAddNodeTail(server.slaves,c);
/* Create the replication backlog if needed. */ if (listLength(server.slaves) == 1 && server.repl_backlog == NULL) { /* When we create the backlog from scratch, we always use a new * replication ID and clear the ID2, since there is no valid * past history. */ changeReplicationId(); clearReplicationId2(); createReplicationBacklog(); serverLog(LL_NOTICE,"Replication backlog created, my new " "replication IDs are '%s' and '%s'", server.replid, server.replid2); }
/* CASE 1: BGSAVE 正在运行中(with disk target) */ if (server.child_type == CHILD_TYPE_RDB && server.rdb_child_type == RDB_CHILD_TYPE_DISK) { /* Ok a background save is in progress. Let's check if it is a good * one for replication, i.e. if there is another slave that is * registering differences since the server forked to save. */ client *slave; listNode *ln; listIter li;
listRewind(server.slaves,&li); while((ln = listNext(&li))) { slave = ln->value; /* 找一找 in progress bgsave 是不是某个 slave 触发的 */ if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END && (!(slave->flags & CLIENT_REPL_RDBONLY) || (c->flags & CLIENT_REPL_RDBONLY))) break; } /* 当前 client(新的 slave),和刚才找到的 slave 支持的能力一致, * 那么 bgsave 的结果新的 slave 也能使用 */ if (ln && ((c->slave_capa & slave->slave_capa) == slave->slave_capa)) { /* 把主节点的 replid 和 offset 发给新的 slave,即回复命令 “+FULLRESYNC replid offset”*/ if (!(c->flags & CLIENT_REPL_RDBONLY)) copyClientOutputBuffer(c,slave); replicationSetupSlaveForFullResync(c,slave->psync_initial_offset); serverLog(LL_NOTICE,"Waiting for end of BGSAVE for SYNC"); } else { /* 本次的 bgsave 结果不能用来创建 replication,只能等下次 bgsave 触发了 */ serverLog(LL_NOTICE,"Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC"); }
/* CASE 2: BGSAVE 正在运行中, with socket target. */ } elseif (server.child_type == CHILD_TYPE_RDB && server.rdb_child_type == RDB_CHILD_TYPE_SOCKET) { /* 已经有 slave 触发 bgsave 了,但是它是 with socket target * 直接把数据写给 socket 的,所以没办法重用。只能等下次 bgsave * 再做同步。*/ serverLog(LL_NOTICE,"Current BGSAVE has socket target. Waiting for next BGSAVE for SYNC");
/* CASE 3: There is no BGSAVE is progress. */ } else { if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF) && server.repl_diskless_sync_delay) { /* diskless replication 会立即把数据写到 socket 里, * 中途再过来的 slave 就只能等下次 rdb 了,所以为了减少压力, * 让后续的 slaves 都尽量重用这次 rdb,所以我们不用着急立刻启动 rdb。 * 而是在 replicationCron() 中启动 Diskless replication RDB */ serverLog(LL_NOTICE,"Delay next BGSAVE for diskless SYNC"); } else { /* 启动 bgsave */ if (!hasActiveChildProcess()) { startBgsaveForReplication(c->slave_capa); } else { serverLog(LL_NOTICE, "No BGSAVE in progress, but another BG operation is active. " "BGSAVE for replication delayed"); } } } return; }
/* 准备开始增量同步: * 1) Set client state to make it a slave. * 2) Inform the client we can continue with +CONTINUE * 3) 发送缓冲区 backlog 中的数据给 slave */ c->flags |= CLIENT_SLAVE; c->replstate = SLAVE_STATE_ONLINE; c->repl_ack_time = server.unixtime; c->repl_put_online_on_ack = 0; listAddNodeTail(server.slaves,c); /* 应答 slave */ if (c->slave_capa & SLAVE_CAPA_PSYNC2) { buflen = snprintf(buf,sizeof(buf),"+CONTINUE %s\r\n", server.replid); } else { buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n"); } if (connWrite(c->conn,buf,buflen) != buflen) { freeClientAsync(c); return C_OK; } // 发送数据给 slave // 这个方法不再展开,实际上就是根据 slave.offset // 把 master 的复制缓冲区 backlog 中剩余的数据发送给 client psync_len = addReplyReplicationBacklog(c,psync_offset); serverLog(LL_NOTICE, "Partial resynchronization request from %s accepted. Sending %lld bytes of backlog starting from offset %lld.", replicationGetSlaveName(c), psync_len, psync_offset); ······ /* 增量同步完成 */ return C_OK;
need_full_resync: // 增量同步 如果返回错误,那么同步的主流程就会去执行 全量同步 /* We need a full resync for some reason... Note that we can't * reply to PSYNC right now if a full SYNC is needed. The reply * must include the master offset at the time the RDB file we transfer * is generated, so we need to delay the reply to that moment. */ return C_ERR; }
/* 这段英文注释没怎么看明白,不过好在代码比较简单,不影响我们理解 * 就是设置了 read handler 和 write handler * * This function is called when successfully setup a partial resynchronization * so the stream of data that we'll receive will start from were this * master left. */ voidreplicationResurrectCachedMaster(connection *conn){ ...... /* Re-add to the list of clients. */ linkClient(server.master);
// read handler 是 readQueryFromClient, // 在 readme.txt 中对 readQueryFromClient 有这样一段说明: // `readQueryFromClient()` is the *readable event handler* and accumulates data read from the client into the query buffer. // 即,增量同步的数据,其实是被当做普通 redis 命令来处理的 if (connSetReadHandler(server.master->conn, readQueryFromClient)) { serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno)); freeClientAsync(server.master); /* Close ASAP. */ }
/* We may also need to install the write handler as well if there is * pending data in the write buffers. */ if (clientHasPendingReplies(server.master)) { if (connSetWriteHandler(server.master->conn, sendReplyToClient)) { serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno)); freeClientAsync(server.master); /* Close ASAP. */ } } }
/* When a background RDB saving/transfer terminates, call the right handler. */ voidbackgroundSaveDoneHandler(int exitcode, int bysignal){ ······ /* Possibly there are slaves waiting for a BGSAVE in order to be served * (the first stage of SYNC is a bulk transfer of dump.rdb) */ updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, type); }
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) { structredis_stat buf; ······
/* 如果 rdb 是在磁盘上的,我们需要将它发送给 slave。 * 如果是 socket 模式的,那么 rdb 信息已经被发送给 slave 了, * 我们只需要将它标记位 online 状态。 */ if (type == RDB_CHILD_TYPE_SOCKET) { serverLog(LL_NOTICE, "Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming", replicationGetSlaveName(slave)); /* Note: we wait for a REPLCONF ACK message from the replica in * order to really put it online (install the write handler * so that the accumulated data can be transferred). However * we change the replication state ASAP, since our slave * is technically online now. * * So things work like that: * * 1. We end trasnferring the RDB file via socket. * 2. The replica is put ONLINE but the write handler * is not installed. * 3. The replica however goes really online, and pings us * back via REPLCONF ACK commands. * 4. Now we finally install the write handler, and send * the buffers accumulated so far to the replica. * * But why we do that? Because the replica, when we stream * the RDB directly via the socket, must detect the RDB * EOF (end of file), that is a special random string at the * end of the RDB (for streamed RDBs we don't know the length * in advance). Detecting such final EOF string is much * simpler and less CPU intensive if no more data is sent * after such final EOF. So we don't want to glue the end of * the RDB trasfer with the start of the other replication * data. */ slave->replstate = SLAVE_STATE_ONLINE; slave->repl_put_online_on_ack = 1; slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */ } else { ······ // 将 rdb 文件发给 slave if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) { freeClient(slave); continue; } } } } }
/* 先给 slave 发送 RDB 文件的总大小 * Before sending the RDB file, we send the preamble as configured by the * replication process. Currently the preamble is just the bulk count of * the file in the form "$<length>\r\n". */ if (slave->replpreamble) { nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble)); ······ atomicIncr(server.stat_net_output_bytes, nwritten); sdsrange(slave->replpreamble,nwritten,-1); if (sdslen(slave->replpreamble) == 0) { sdsfree(slave->replpreamble); slave->replpreamble = NULL; /* fall through sending data. */ } else { return; } }
/* 发送 RDB data. */ ······ if ((nwritten = connWrite(conn,buf,buflen)) == -1) { if (connGetState(conn) != CONN_STATE_CONNECTED) { serverLog(LL_WARNING,"Write error sending DB to replica: %s", connGetLastError(conn)); freeClient(slave); } return; } slave->repldboff += nwritten; atomicIncr(server.stat_net_output_bytes, nwritten); if (slave->repldboff == slave->repldbsize) { close(slave->repldbfd); slave->repldbfd = -1; connSetWriteHandler(slave->conn,NULL); putSlaveOnline(slave); } }
/* 在加载 RDB 到内存文件之前,需要关闭 conn 的 read handler, * 否则每当我们读数据的时候,eventLoop 就会被唤醒,然后调用 rdbLoad()。 * Before loading the DB into memory we need to delete the readable * handler, otherwise it will get called recursively since * rdbLoad() will call the event loop to process events from time to * time for non blocking loading. */ connSetReadHandler(conn, NULL); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory"); rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
/* 场景1:不使用磁盘做 replication */ if (use_diskless_load) { rio rdb; rioInitWithConn(&rdb,conn,server.repl_transfer_size);
// 把接收到的 tmp file,rename 成正式文件 if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) { serverLog(LL_WARNING, "Failed trying to rename the temp DB into %s in " "MASTER <-> REPLICA synchronization: %s", server.rdb_filename, strerror(errno)); cancelReplicationHandshake(1); if (old_rdb_fd != -1) close(old_rdb_fd); return; } ······ // 加载 RDB 文件中的数据 if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) { serverLog(LL_WARNING, "Failed trying to load the MASTER synchronization " "DB from disk"); cancelReplicationHandshake(1); if (server.rdb_del_sync_files && allPersistenceDisabled()) { serverLog(LL_NOTICE,"Removing the RDB file obtained from " "the master. This replica has persistence " "disabled"); bg_unlink(server.rdb_filename); } /* Note that there's no point in restarting the AOF on sync failure, it'll be restarted when sync succeeds or replica promoted. */ return; }
/* This function should be called just after a replica received the RDB file * for the initial synchronization, and we are finally ready to send the * incremental stream of commands. * * replica 接受 rdb file 之后,会立刻调用这个方法,此时,我们就可以 * 把增量的命令发给 slave 了 * * 它的主要功能是重新 install writable event,它在 sync 命令开始的时候就被移除了, * 原因是防止 rdb file 和 增量命令 混淆。等我们传完 rdb file,就可以装载 * writable event 了,然后通过它,就可以发送增量的命令了(replication buffer) * * 注: * rdb file 是通过直接往 socket 写数据的方式发送的 * 而增量命令,是先写在 socket 的 buffer 里,然后异步地通过事件驱动(writable event)的方式发送的。 */ voidreplicaStartCommandStream(client *slave){ slave->repl_start_cmd_stream_on_ack = 0; ······ clientInstallWriteHandler(slave); }
/* This function puts the client in the queue of clients that should write * their output buffers to the socket. Note that it does not *yet* install * the write handler, to start clients are put in a queue of clients that need * to write, so we try to do that before returning in the event loop (see the * handleClientsWithPendingWrites() function). * If we fail and there is more data to write, compared to what the socket * buffers can hold, then we'll really install the handler. */ voidclientInstallWriteHandler(client *c){ /* Schedule the client to write the output buffers to the socket only * if not already done and, for slaves, if the slave can actually receive * writes at this stage. */ if (!(c->flags & CLIENT_PENDING_WRITE) && (c->replstate == REPL_STATE_NONE || (c->replstate == SLAVE_STATE_ONLINE && !c->repl_start_cmd_stream_on_ack))) { /* Here instead of installing the write handler, we just flag the * client and put it into a list of clients that have something * to write to the socket. This way before re-entering the event * loop, we can try to directly write to the client sockets avoiding * a system call. We'll only really install the write handler if * we'll not be able to write the whole reply at once. */ c->flags |= CLIENT_PENDING_WRITE; listAddNodeHead(server.clients_pending_write,c); } }
/* 原来是为了减少系统调用(install handler、handler called) * This function is called just before entering the event loop, in the hope * we can just write the replies to the client output buffer without any * need to use a syscall in order to install the writable event handler, * get it called, and so forth. */ inthandleClientsWithPendingWrites(void){ listIter li; listNode *ln; int processed = listLength(server.clients_pending_write);
listRewind(server.clients_pending_write,&li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); c->flags &= ~CLIENT_PENDING_WRITE; listDelNode(server.clients_pending_write,ln); ······ /* Try to write buffers to the client socket. */ if (writeToClient(c,0) == C_ERR) continue; ······ } } return processed; }
staticvoidpropagateNow(int dbid, robj **argv, int argc, int target){ if (!shouldPropagate(target)) return;
/* This needs to be unreachable since the dataset should be fixed during * client pause, otherwise data may be lost during a failover. */ serverAssert(!(areClientsPaused() && !server.client_pause_in_transaction));
if (server.aof_state != AOF_OFF && target & PROPAGATE_AOF) feedAppendOnlyFile(dbid,argv,argc); if (target & PROPAGATE_REPL) replicationFeedSlaves(server.slaves,dbid,argv,argc); }
/* 只有 master 能使用这个方法,其他情况退出 */ if (server.masterhost != NULL) return;
/* no slaves, no backlog buffer */ if (server.repl_backlog == NULL && listLength(slaves) == 0) return; ···· /* Send SELECT command to every slave if needed. */ if (server.slaveseldb != dictid) { robj *selectcmd;
/* For a few DBs we have pre-computed SELECT command. */ if (dictid >= 0 && dictid < PROTO_SHARED_SELECT_CMDS) { selectcmd = shared.select[dictid]; } else { int dictid_len;
if (dictid < 0 || dictid >= PROTO_SHARED_SELECT_CMDS) decrRefCount(selectcmd);
server.slaveseldb = dictid; }
/* Write the command to the replication buffer if any. */ char aux[LONG_STR_SIZE+3];
/* Add the multi bulk reply length. */ aux[0] = '*'; len = ll2string(aux+1,sizeof(aux)-1,argc); aux[len+1] = '\r'; aux[len+2] = '\n'; feedReplicationBuffer(aux,len+3);
for (j = 0; j < argc; j++) { long objlen = stringObjectLen(argv[j]);
/* We need to feed the buffer with the object as a bulk reply * not just as a plain string, so create the $..CRLF payload len * and add the final CRLF */ aux[0] = '$'; len = ll2string(aux+1,sizeof(aux)-1,objlen); aux[len+1] = '\r'; aux[len+2] = '\n'; feedReplicationBuffer(aux,len+3); feedReplicationBufferWithObject(argv[j]); feedReplicationBuffer(aux+len+1,2); } }
三、过期 key 的淘汰(つづく)
这篇文章写太久了,需要换换脑子,这部分内容暂时搁置
四、后记
在这片文章写到差不多 80% 的时候,在 redis 的 readme.txt 中看到这段话
1 2 3 4 5 6
replication.c
This is one of the most complex files inside Redis, it is recommended to approach it only after getting a bit familiar with the rest of the code base. In this file there is the implementation of both the master and replica role of Redis.