/* 复制核心函数,每秒执行一次,在serverCron中调用 */
void replicationCron(void) {
static long long replication_cron_loops = 0;
/* 当前实例作为slave,正准备握手或者正在握手:[2,13],超时,则取消当前握手,重连 */
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();
}
/* 当前实例作为slave,正在接受RDB: [14],如果超时,取消当前握手,重连 */
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();
}
/* 当前实例作为slave, 正在进行正常的复制,但是出现了超时,则释放master客户端,重连 */
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);
}
/* 当前实例作为slave, 需要连接一个master */
if (server.repl_state == REPL_STATE_CONNECT) {
serverLog(LL_NOTICE,"Connecting to MASTER %s:%d",
server.masterhost, server.masterport);
if (connectWithMaster() == C_OK) {
serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");
}
}
/* 当前实例作为slave, 并且master可以理解PSYNC,发送REPLCONf ACK给master,每秒1次,作用:
* 1) slave->master的心跳
* 2) 同步给master当前slave的复制偏移量
* 注意:如果master不支持PSYNC和复制偏移量。
* */
if (server.masterhost && server.master &&
!(server.master->flags & CLIENT_PRE_PSYNC))
replicationSendAck();
/* 如果我们有slaves,不断地PING它们。
* 这样slaves可以显式的实现一个到master的超时,并且能在TCP不可用时,监测到断连。 */
listIter li;
listNode *ln;
robj *ping_argv[1];
/* 当时实例作为master向slaves发送PING,每repl_ping_slave_period=10s一次
* 注意:这个功能实际上是通过写入到复制积压缓冲区中实现的,而不是直接发送给slave。
* 这意味着,在复制同步期间,所有的PING将会在slave同步期间不会真正发送给slave,
* 而是在slave的复制状态变更为ONLINE时才发送。
* */
if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&
listLength(server.slaves))
{
ping_argv[0] = createStringObject("PING",4);
replicationFeedSlaves(server.slaves, server.slaveseldb,
ping_argv, 1);
decrRefCount(ping_argv[0]);
}
/* 对于presync阶段的slaves,它们正在等待RDB文件,发送一个空行。
* 对于presync阶段的slaves,它们正在等待RDB文件,而RDB生成的时间可能很长,我们需要让slave
* 能够知道这段时间内到master的连接正常,这是必要的。
* 注意:在线状态时master->slave的心跳是通过复制流中的PING实现的,而sub-slaves间的复制流是
* 是从顶级master继承的,此时还在presync阶段,没有复制流传输,所以PING心跳无法用于presync阶段。
* 这个presync阶段的newline的心跳机制不会影响复制流偏移量。
* 这个newline将会被slave忽略,但是会刷新slave和master的最后交互时间来防止超时,我们每秒发送一次。
* */
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = ln->value;
int is_presync =
(slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||
(slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
if (is_presync) {
if (write(slave->fd, "\n", 1) == -1) {
/* 不必关心socket errors,这只是用于RDB生成期间的心跳 */
}
}
}
...
}
/* 连接上master
* 1. 创建socket,连接上master
* 2. 注册该socket的读写时间为syncWithMaster函数
* 3. 修改复制相关状态
* */
int connectWithMaster(void) {
int fd;
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
if (fd == -1) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
strerror(errno));
return C_ERR;
}
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
AE_ERR)
{
close(fd);
serverLog(LL_WARNING,"Can't create readable event for SYNC");
return C_ERR;
}
server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_s = fd;
server.repl_state = REPL_STATE_CONNECTING;
return C_OK;
}
/* 这个函数将会在一个非阻塞的客户端连接到master后触发 */
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5;
int sockerr = 0, psync_result;
socklen_t errlen = sizeof(sockerr);
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* 当用户将服务器切换到master后,关闭socket */
if (server.repl_state == REPL_STATE_NONE) {
close(fd);
return;
}
/* 一个非阻塞的连接可能会触发error,如果出现了错误,我们跳转到error */
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
sockerr = errno;
if (sockerr) {
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
strerror(sockerr));
goto error;
}
/* 发送一个PING命令检查master是否有能力回复 */
if (server.repl_state == REPL_STATE_CONNECTING) {
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
// 删除写事件,我们只关注PONG回复,切换状态
aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
server.repl_state = REPL_STATE_RECEIVE_PONG;
// 发送PING,如果出现了错误,那么就跳转到write_error
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
if (err) goto write_error;
return;
}
/* Receive the PONG command. */
if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
/* +PONG 正常响应
* --NOAUTH 需要进行验证
* -ERR operation not permitted 老版本,需要进行验证
* */
if (err[0] != '+' &&
strncmp(err,"-NOAUTH",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...");
}
sdsfree(err);
// 切换到待发送AUTH状态
server.repl_state = REPL_STATE_SEND_AUTH;
}
/* 如果master需要,我们就进行验证,否则我们切换到待发送端口状态 */
if (server.repl_state == REPL_STATE_SEND_AUTH) {
if (server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH;
return;
} else {
server.repl_state = REPL_STATE_SEND_PORT;
}
}
/* 接收AUTH回复,如果通过,切换到待发送端口状态 */
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
if (err[0] == '-') {
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
sdsfree(err);
goto error;
}
sdsfree(err);
server.repl_state = REPL_STATE_SEND_PORT;
}
/* 如果配置了server.slave_announce_port,则使用slave_announce_port,否则使用server.port */
if (server.repl_state == REPL_STATE_SEND_PORT) {
sds port = sdsfromlonglong(server.slave_announce_port ?
server.slave_announce_port : server.port);
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
"listening-port",port, NULL);
sdsfree(port);
if (err) goto write_error;
sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_PORT;
return;
}
/* 接收 REPLCONF listeniing-port [port] 的响应 */
if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
/*忽略,不是所有redis版本都支持 REPLCONF listening-port. */
if (err[0] == '-') {
serverLog(LL_NOTICE,"(Non critical) Master does not understand "
"REPLCONF listening-port: %s", err);
}
sdsfree(err);
server.repl_state = REPL_STATE_SEND_IP;
}
/* 如果没有配置slave-announce-ip,则跳过,master可以直接从socket中知道ip */
if (server.repl_state == REPL_STATE_SEND_IP &&
server.slave_announce_ip == NULL)
{
server.repl_state = REPL_STATE_SEND_CAPA;
}
/* 发送 REPLCONF ip-address [ip] */
/* 设置slave的ip,这样对于端口转发和NAT的场景,master的INFO命令将能够列出正确的slave的ip */
if (server.repl_state == REPL_STATE_SEND_IP) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
"ip-address",server.slave_announce_ip, NULL);
if (err) goto write_error;
sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_IP;
return;
}
/* 接收 REPLCONF ip-address [ip] 的响应 */
if (server.repl_state == REPL_STATE_RECEIVE_IP) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
/* 忽略,不是所有redis版本都支持 REPLCONF ip-address */
if (err[0] == '-') {
serverLog(LL_NOTICE,"(Non critical) Master does not understand "
"REPLCONF ip-address: %s", err);
}
sdsfree(err);
server.repl_state = REPL_STATE_SEND_CAPA;
}
/* 宣告我们的slave支持的能力
* EOF:支持EOF格式的RDB无盘复制
* PSYNC2:支持PSYNC v2,所以能够理解 +CONTINUE
*
* master将会忽略它无法理解的能力
* */
if (server.repl_state == REPL_STATE_SEND_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
"capa","eof","capa","psync2",NULL);
if (err) goto write_error;
sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_CAPA;
return;
}
/* 接收CAPA回复 */
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
/* 忽略,不是所有redis版本都支持 REPLCONF capa. */
if (err[0] == '-') {
serverLog(LL_NOTICE,"(Non critical) Master does not understand "
"REPLCONF capa: %s", err);
}
sdsfree(err);
server.repl_state = REPL_STATE_SEND_PSYNC;
}
/* 尝试执行一个部分重同步。
* 如果没有cached master,我们将会使用PSYNC来开启一个完整重同步,这样我们可以
* 获得master runid和全局偏移量,我们将会在下次重连时开启一个部分重同步。 */
if (server.repl_state == REPL_STATE_SEND_PSYNC) {
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
err = sdsnew("Write error sending the PSYNC command.");
goto write_error;
}
server.repl_state = REPL_STATE_RECEIVE_PSYNC;
return;
}
/* 如果我们到达了这里,我们应该处于REPL_STATE_RECEIVE_PSYNC状态 */
if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {
serverLog(LL_WARNING,"syncWithMaster(): state machine error, "
"state should be RECEIVE_PSYNC but is %d",
server.repl_state);
goto error;
}
/* 获取PSYNC的响应 */
psync_result = slaveTryPartialResynchronization(fd,1);
if (psync_result == PSYNC_WAIT_REPLY) return; /* 暂时没有数据,继续获取 */
/* master暂时无法进行复制,我们之后需要从零开始复制,所以我们转到err。
* 当master正在loading或者master未连接到它的master等情况时发生。 */
if (psync_result == PSYNC_TRY_LATER) goto error;
/* 注意:如果PSYNC没有返回WAIT_REPLY,它会自己处理卸载可读事件处理器 */
if (psync_result == PSYNC_CONTINUE) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
return;
}
/* PSYNC失败或者不支持:如果我们有sub-slaves,我们希望它们和我们重新进行同步。
* 因为master可能会传输给我们一个全新的数据集,我们不能增量传播给slaves。 */
disconnectSlaves(); /* 强制我们的slaves重新同步我们 */
freeReplicationBacklog(); /* 不允许我们的slaves进行PSYNC */
/* 不支持PSYNC命令,我们将会在后面用SYNC重试 */
if (psync_result == PSYNC_NOT_SUPPORTED) {
serverLog(LL_NOTICE,"Retrying with SYNC...");
if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno));
goto error;
}
}
/* 准备一个合适的临时文件来接收RDB数据 */
while(maxtries--) {
snprintf(tmpfile,256,
"temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
if (dfd != -1) break;
sleep(1);
}
if (dfd == -1) {
serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno));
goto error;
}
/* 设置非阻塞下载RDB文件的可读事件处理器 */
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)
== AE_ERR)
{
serverLog(LL_WARNING,
"Can't create readable event for SYNC: %s (fd=%d)",
strerror(errno),fd);
goto error;
}
// 初始化相关状态
server.repl_state = REPL_STATE_TRANSFER;
server.repl_transfer_size = -1;
server.repl_transfer_read = 0;
server.repl_transfer_last_fsync_off = 0;
server.repl_transfer_fd = dfd;
server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_tmpfile = zstrdup(tmpfile);
return;
error: // 如果出现了任何错误,我们将会重置关闭socket,删除ae注册时间,并重置复制状态以重连
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd);
close(fd);
server.repl_transfer_s = -1;
server.repl_state = REPL_STATE_CONNECT;
return;
write_error: // 处理同步发送命令的错误
serverLog(LL_WARNING,"Sending command to master in replication handshake: %s", err);
sdsfree(err);
goto error;
}
主服务器处理从服务器发来的replconf命令
/* REPLCONF
...
* 这个命令用于在SYNC命令之前配置复制进程。
* 当前这个命令用于和master通信,
* 1) 用来告诉master端口号等相关信息,使得master的info输出中可以正确的输出相关信息。
* 2) 也用来告诉master复制过程中的相关配置信息如CAPA来让master决策。
* */
void replconfCommand(client *c) {
int j;
if ((c->argc % 2) == 0) {
/* Number of arguments must be odd to make sure that every
* option has a corresponding value. */
addReply(c,shared.syntaxerr);
return;
}
/* Process every option-value pair. */
for (j = 1; j < c->argc; j+=2) {
if (!strcasecmp(c->argv[j]->ptr,"listening-port")) {
long port;
if ((getLongFromObjectOrReply(c,c->argv[j+1],
&port,NULL) != C_OK))
return;
c->slave_listening_port = port;
} else if (!strcasecmp(c->argv[j]->ptr,"ip-address")) {
sds ip = c->argv[j+1]->ptr;
if (sdslen(ip) < sizeof(c->slave_ip)) {
memcpy(c->slave_ip,ip,sdslen(ip)+1);
} else {
addReplyErrorFormat(c,"REPLCONF ip-address provided by "
"replica instance is too long: %zd bytes", sdslen(ip));
return;
}
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
/* Ignore capabilities not understood by this master. */
if (!strcasecmp(c->argv[j+1]->ptr,"eof"))
c->slave_capa |= SLAVE_CAPA_EOF;
else if (!strcasecmp(c->argv[j+1]->ptr,"psync2"))
c->slave_capa |= SLAVE_CAPA_PSYNC2;
} else if (!strcasecmp(c->argv[j]->ptr,"ack")) {
/* REPLCONF ACK is used by slave to inform the master the amount
* of replication stream that it processed so far. It is an
* internal only command that normal clients should never use. */
long long offset;
if (!(c->flags & CLIENT_SLAVE)) return;
if ((getLongLongFromObject(c->argv[j+1], &offset) != C_OK))
return;
if (offset > c->repl_ack_off)
c->repl_ack_off = offset;
c->repl_ack_time = server.unixtime;
/* If this was a diskless replication, we need to really put
* the slave online when the first ACK is received (which
* confirms slave is online and ready to get more data). */
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
putSlaveOnline(c);
/* Note: this command does not reply anything! */
return;
} else if (!strcasecmp(c->argv[j]->ptr,"getack")) {
/* REPLCONF GETACK is used in order to request an ACK ASAP
* to the slave. */
if (server.masterhost && server.master) replicationSendAck();
return;
} else {
addReplyErrorFormat(c,"Unrecognized REPLCONF option: %s",
(char*)c->argv[j]->ptr);
return;
}
}
addReply(c,shared.ok);
}
同步策略协调
从服务器询问同步策略
/* 如果我们正在重连,尝试一个部分重同步。
* 如果没有cached master,我们将会发送'PSYNC ? -1'来触发一个全同步,来获取master的
* runid和复制全局offset。
*
* 这个函数被syncWithMaster调用,所以下面的假设成立:
* 1) 入参fd已经连接上。
* 2) 这个函数不会关闭fd,然而,当部分重同步成功时,server.master的client结构体将会重用fd。
*
* 这个函数有两种行为,通过read_reply控制:
* 如果read_reply为0,我们将会发送PSYNC命令到master,并且调用方需要在之后使用read_reply=1,
* 再次调用该函数来读取master的回复。这是为了支持非阻塞的操作,我们在两次事件循环中分别进行读写
* 来发送命令和获取响应。
*
* 当read_reply=0时,即发送命令时,如果发送出错,将会返回PSYNC_WRITE_ERR,否则将会返回
* PSYNC_WAIT_REPLY,并且调用方需要使用read_reply=1再次调用该函数来读取master的回复。
* 当read_reply=1时,当还没有有效的数据时,将再次返回PSYNC_WAIT_REPLY,
*
* 函数返回值:
* PSYNC_CONTINUE: 可以进行部分重同步。
* PSYNC_FULLRESYNC:master支持PSYNC命令,但是需要进行完整重同步。master runid和offset将会被保存。
* PSYNC_NOT_SUPPORTED: master不理解PSYNC命令,调用方需要使用SYNC命令再次调用。
* PSYNC_WRITE_ERROR:写命令时出现了错误。
* PSYNC_WAIT_REPLY:暂时没有有效的数据,需要后续使用read_reply=1再次调用。
* PSYNC_TRY_LATER: master暂时无法处理,比如正在load数据或者master没联系上自己的master,等等。
*
* 副作用:
* 1) 这个将会移除读事件处理器,除非返回了PSYNC_WAIT_REPLY。
* 2) server.master_initial_offset将会根据master的reply正确的设置,这个值将会被用来
* 填充server.master结构体的复制偏移量。
* */
#define PSYNC_WRITE_ERROR 0
#define PSYNC_WAIT_REPLY 1
#define PSYNC_CONTINUE 2
#define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5
int slaveTryPartialResynchronization(int fd, int read_reply) {
char *psync_replid;
char psync_offset[32];
sds reply;
if (!read_reply) { // read_reply=0,向master发送PSYNC
/* 初始化master_initial_offset=-1来标记不可用。
* 之后我们如果要做完整重同步我们将会设置其为正确的值,然后这个值将会传递到server.master。*/
server.master_initial_offset = -1;
if (server.cached_master) { // 如果我们有一个缓存的master信息,我们将会使用该信息做部分重同步
psync_replid = server.cached_master->replid;
// 注意:PSYNC发送的偏移量是reploff+1,表示slave需要的首个数据
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
serverLog(LL_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_replid, psync_offset);
} else { // 如果没有,我们只能进行一个完整重同步
serverLog(LL_NOTICE,"Partial resynchronization not possible (no cached master)");
psync_replid = "?";
memcpy(psync_offset,"-1",3);
}
/* 发送PSYNC命令 */
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL);
if (reply != NULL) {
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
sdsfree(reply);
// 发送PSYNC出错,删除注册的读事件
aeDeleteFileEvent(server.el,fd,AE_READABLE);
return PSYNC_WRITE_ERROR;
}
return PSYNC_WAIT_REPLY;
}
// read_reply=1, 之后后面的逻辑,读取PSYNC的响应
/* 从响应中读取一行命令回复 */
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
if (sdslen(reply) == 0) {
/* 在它接受到PSYNC回复前后,master可能会返回一些空白行用于心跳,我们保持连接即可 */
sdsfree(reply);
return PSYNC_WAIT_REPLY;
}
aeDeleteFileEvent(server.el,fd,AE_READABLE);
// +FULLRESYNC bc1621104063d4f46cff756b644c290e80362d3c 238
if (!strncmp(reply,"+FULLRESYNC",11)) {
char *replid = NULL, *offset = NULL;
/* 如果master要求我们做完整重同步,解析reply来提取runid和复制偏移量 */
replid = strchr(reply,' ');
if (replid) {
replid++;
offset = strchr(replid,' ');
if (offset) offset++;
}
if (!replid || !offset || (offset-replid-1) != CONFIG_RUN_ID_SIZE) {
serverLog(LL_WARNING,
"Master replied with wrong +FULLRESYNC syntax.");
/* 这是一种异常情况,master返回了+FULLRESYNC,说明master支持PSYNC,
* 但是返回的格式看起来有问题。
* 为了保证安全,我们把replid置为空, 防止等会重连master时使用一个错误的replid */
memset(server.master_replid,0,CONFIG_RUN_ID_SIZE+1);
} else {
memcpy(server.master_replid, replid, offset-replid-1);
server.master_replid[CONFIG_RUN_ID_SIZE] = '\0';
server.master_initial_offset = strtoll(offset,NULL,10);
serverLog(LL_NOTICE,"Full resync from master: %s:%lld",
server.master_replid,
server.master_initial_offset);
}
/* 我们要执行完整重同步了,抛弃cached master结构体。 */
replicationDiscardCachedMaster();
sdsfree(reply);
return PSYNC_FULLRESYNC;
}
// +CONTINUE
if (!strncmp(reply,"+CONTINUE",9)) {
/* 部分重同步被接受 */
serverLog(LL_NOTICE,
"Successful partial resynchronization with master.");
/* 检查master宣告的复制偏移量。
* 如果它改变,我们需要把新的id作为id,并把之前的id作为次id2,并更新second_replid_offset,
* 这样我们的子slaves可以在断开后使用PSYNC重连我们。 */
char *start = reply+10;
char *end = reply+9;
while(end[0] != '\r' && end[0] != '\n' && end[0] != '\0') end++;
if (end-start == CONFIG_RUN_ID_SIZE) {
char new[CONFIG_RUN_ID_SIZE+1];
memcpy(new,start,CONFIG_RUN_ID_SIZE);
new[CONFIG_RUN_ID_SIZE] = '\0';
if (strcmp(new,server.cached_master->replid)) { // masterID改变
serverLog(LL_WARNING,"Master replication ID changed to %s",new);
/* 设置oldID作为我们的id2,并更新second_replid_offset */
memcpy(server.replid2,server.cached_master->replid,
sizeof(server.replid2));
server.second_replid_offset = server.master_repl_offset+1;
/* 更新ceched->masterID和id */
memcpy(server.replid,new,sizeof(server.replid));
memcpy(server.cached_master->replid,new,sizeof(server.replid));
/* 断开所有的slave,之后它们将会重连,并使用我们新的id */
disconnectSlaves();
}
}
/* 设置继续复制 */
sdsfree(reply);
replicationResurrectCachedMaster(fd);
/* 如果当前的实例是我们重启的,并且PSYNC的元数据是从持久化文件中读取的,
* 我们的复制缓冲区应该还没有初始化,创建它。 */
if (server.repl_backlog == NULL) createReplicationBacklog();
return PSYNC_CONTINUE;
}
/* 如果我们达到了这里说明我们遇到了某些错误。
* 当错误是临时错误时,我们返回PSYNC_TRY_LATER
* 当master不支持PSYNC或者我们不清楚的错误时,我们返回PSYNC_NOT_SUPPORTED */
if (!strncmp(reply,"-NOMASTERLINK",13) ||
!strncmp(reply,"-LOADING",8))
{
serverLog(LL_NOTICE,
"Master is currently unable to PSYNC "
"but should be in the future: %s", reply);
sdsfree(reply);
return PSYNC_TRY_LATER;
}
if (strncmp(reply,"-ERR",4)) {
/* If it's not an error, log the unexpected event. */
serverLog(LL_WARNING,
"Unexpected reply to PSYNC from master: %s", reply);
} else {
serverLog(LL_NOTICE,
"Master does not support PSYNC or is in "
"error state (reply: %s)", reply);
}
sdsfree(reply);
replicationDiscardCachedMaster();
return PSYNC_NOT_SUPPORTED;
}
主服务器响应同步策略
/* SYNC和PSYNC命令的实现 */
void syncCommand(client *c) {
/* 如果客户端已经是一个slave或者monitor,则直接返回 */
if (c->flags & CLIENT_SLAVE) return;
/* 如果我们是一个slave但是还没有跟随成功我们的master,则拒绝 */
if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED) {
addReplySds(c,sdsnew("-NOMASTERLINK Can't SYNC while not connected with my master\r\n"));
return;
}
/* 当执行同步时,client的回复缓冲区必须是空的。我们在执行BGSAVE时,可能复用RDB文件,
* 会在两个slave之间拷贝回复缓冲区 */
if (clientHasPendingReplies(c)) {
addReplyError(c,"SYNC and PSYNC are invalid with pending output");
return;
}
serverLog(LL_NOTICE,"Replica %s asks for synchronization",
replicationGetSlaveName(c));
/* 如果是PSYNC命令,尝试进行部分重同步。
* 如果失败了,我们将会进行一个完整重同步,通过返回:
* +FULLRESYNC
* 这样slave可以在断开到master的连接时,知道新的replid和偏移量,来进行一个部分重同步的重连。
* */
if (!strcasecmp(c->argv[0]->ptr,"psync")) {
if (masterTryPartialResynchronization(c) == C_OK) {
server.stat_sync_partial_ok++;
return; /* 不需要完整重同步,直接返回 */
} else {
char *master_replid = c->argv[1]->ptr;
/* 仅当slave被迫执行部分重同步出错时,才更新stat_sync_partial_err */
if (master_replid[0] != '?') server.stat_sync_partial_err++;
}
} else {
/* 如果是SYNC命令,我们将会使用复制协议的一个老的实现。标记客户端,我们不希望接受
* REPLCONF ACK的反馈信息。 */
c->flags |= CLIENT_PRE_PSYNC;
}
/* 下面进行完整重同步 */
server.stat_sync_full++;
/* 设置slave复制状态为SLAVE_STATE_WAIT_BGSAVE_START。下面的代码路径将会根据
* 我们不同的处理来修改状态。 */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay) // 如果禁用TCP NoDelay选项
anetDisableTcpNoDelay(NULL, c->fd); /* 失败了也不要紧 */
c->repldbfd = -1;
c->flags |= CLIENT_SLAVE;
listAddNodeTail(server.slaves,c);
/* 如果需要,我们创建repl_backlog缓冲区 */
if (listLength(server.slaves) == 1 && server.repl_backlog == NULL) {
/* 当我们创建backlog时,我们总是使用新的replid并且清理id2,
* 这样就不会有非法的历史数据了 */
changeReplicationId();
clearReplicationId2();
createReplicationBacklog();
}
/* 第一种情况:BGSAVE正在后台进行,并且target=disk */
if (server.rdb_child_pid != -1 &&
server.rdb_child_type == RDB_CHILD_TYPE_DISK)
{
/* 现在后台有一个BGSAVE在运行。我们检查是否可以用于复制。
* 如果有另外一个slave触发了BGSAVE,我们可以尝试复用RDB文件 */
client *slave;
listNode *ln;
listIter li;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
slave = ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) break;
}
/* 如果我们的slave的能力兼容这个slave的能力,则可以复用同一个RDB文件 */
if (ln && ((c->slave_capa & slave->slave_capa) == slave->slave_capa)) {
/* 完成,我们和另外一个slave兼容,设置正确的状态,并copy缓冲区 */
copyClientOutputBuffer(c,slave);
replicationSetupSlaveForFullResync(c,slave->psync_initial_offset);
serverLog(LL_NOTICE,"Waiting for end of BGSAVE for SYNC");
} else {
/* 不兼容,我们需要等待下一个BGSAVE */
serverLog(LL_NOTICE,"Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC");
}
/* 第二种情况:BGSAVE在后台运行,但是target=socket */
} else if (server.rdb_child_pid != -1 &&
server.rdb_child_type == RDB_CHILD_TYPE_SOCKET)
{
/* 又一个RDB进程正直写入socket。我们需要等待下一次BGSAVE来执行同步。 */
serverLog(LL_NOTICE,"Current BGSAVE has socket target. Waiting for next BGSAVE for SYNC");
/* 没有BGSAVE在后台运行 */
} else {
if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF)) {
/* diskless复制RDB进程将会在replicationCron()函数中创建,
* 因为我们想延迟它一段时间来等待更多的slaves。 */
if (server.repl_diskless_sync_delay)
serverLog(LL_NOTICE,"Delay next BGSAVE for diskless SYNC");
} else {
/* 如果target=disk,或者slave无法支持diskless复制,并且我们还没有RDB进程,开始一个。 */
if (server.aof_child_pid == -1) { // 如果没有aof重写进程,则开启
startBgsaveForReplication(c->slave_capa);
} else { // 如果有aof重写进程,则延迟复制
serverLog(LL_NOTICE,
"No BGSAVE in progress, but an AOF rewrite is active. "
"BGSAVE for replication delayed");
}
}
}
return;
}
/* 这个函数从master的视角处理PSYNC请求。
* 可以进行部分重同步返回C_OK,需要进行完整重同步返回C_ERR。*/
int masterTryPartialResynchronization(client *c) {
long long psync_offset, psync_len;
char *master_replid = c->argv[1]->ptr;
char buf[128];
int buflen;
/* 解析slave指定的复制偏移量。
* 如果解析错误,则进行完整重同步:这一般不会发生,但是处理这种情况可以提高鲁棒性。 */
if (getLongLongFromObjectOrReply(c,c->argv[2],&psync_offset,NULL) !=
C_OK) goto need_full_resync;
/* server的replid是否和slave宣告的一致。
* 如果replid改变了,这个master将会有一个不同的复制历史,就不能进行完整重同步。
* 注意:那里有两个潜在的合法replid。然而id2是否合法取决于指定的偏移量。
* 如果replid和server.replid2相同,说明master和slave曾经都作为slave同步过,
* 后来进行了故障转移,则仅当slave没有该新的slave复制的多时,才允许部分重同步。
* */
if (strcasecmp(master_replid, server.replid) &&
(strcasecmp(master_replid, server.replid2) ||
psync_offset > server.second_replid_offset))
{
/* replid=?意味这slave要求进行完整重同步 */
if (master_replid[0] != '?') {
if (strcasecmp(master_replid, server.replid) &&
strcasecmp(master_replid, server.replid2))
{
serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
"Replication ID mismatch (Replica asked for '%s', my "
"replication IDs are '%s' and '%s')",
master_replid, server.replid, server.replid2);
} else {
serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
"Requested offset for second ID was %lld, but I can reply "
"up to %lld", psync_offset, server.second_replid_offset);
}
} else {
serverLog(LL_NOTICE,"Full resync requested by replica %s",
replicationGetSlaveName(c));
}
goto need_full_resync;
}
/* 检查我们是否有slave需要的数据 */
if (!server.repl_backlog ||
psync_offset < server.repl_backlog_off ||
psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
{
serverLog(LL_NOTICE,
"Unable to partial resync with replica %s for lack of backlog (Replica request was: %lld).", replicationGetSlaveName(c), psync_offset);
if (psync_offset > server.master_repl_offset) {
serverLog(LL_WARNING,
"Warning: replica %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
}
goto need_full_resync;
}
/* 如果到达了这里,说明我们可以执行一个部分重同步:
* 1) 设置当前的client为slave。
* 2) 使用+CONTINUE通告客户端我们可以进行部分重同步。
* 3) 发送复制缓冲区中的数据。
* */
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);
/* 在这个阶段,我们不使用client的输出缓冲区时为了加速新的命令。
* 但是,此时我们确定这个socket的发送缓冲区是空的,因此我们的写操作实际上不会错误。 */
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 (write(c->fd,buf,buflen) != buflen) { // 出现了短写,断开重来
freeClientAsync(c);
return C_OK;
}
// 发送复制缓冲区中积累的数据
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);
/* 注意:我们不需要设置server.slaveseldb为-1来强制master发射SELECT,因为这个slave
* 从先前到master的连接中获取了它自己的状态 */
refreshGoodSlavesCount(); // 刷新存活的slave的数量
return C_OK; /* 返回OK,表示进行部分重同步*/
need_full_resync:
/* 我们需要一个完整重同步。
* 注意:我们不能理解回复给slave一个PSYNC。PSYNC的回复里需要包含生成RDB文件时master的offset,
* 因此我们需要延迟回复。*/
return C_ERR;
}
进行部分重同步:
从服务器复活cachedMaster
/* 复活cached master为当前的master,并使用一个新的文件描述符作为socket参数。
* 这个函数在成功设置部分重同步时调用,我们可以继续接受master剩下的数据。
* */
void replicationResurrectCachedMaster(int newfd) {
server.master = server.cached_master;
server.cached_master = NULL;
server.master->fd = newfd;
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
server.master->authenticated = 1;
server.master->lastinteraction = server.unixtime;
server.repl_state = REPL_STATE_CONNECTED;
server.repl_down_since = 0;
/* 重新增加client到链表中 */
linkClient(server.master);
if (aeCreateFileEvent(server.el, newfd, AE_READABLE,
readQueryFromClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */
}
/* 如果有未发送的数据在写缓冲区中,我们需要安装可写事件处理器 */
if (clientHasPendingReplies(server.master)) {
if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE,
sendReplyToClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */
}
}
}
...
* 这个命令用于在SYNC命令之前配置复制进程。
* 当前这个命令用于和master通信,
* 1) 用来告诉master端口号等相关信息,使得master的info输出中可以正确的输出相关信息。
* 2) 也用来告诉master复制过程中的相关配置信息如CAPA来让master决策。
* */
void replconfCommand(client *c) {
int j;
if ((c->argc % 2) == 0) {
/* Number of arguments must be odd to make sure that every
* option has a corresponding value. */
addReply(c,shared.syntaxerr);
return;
}
/* Process every option-value pair. */
for (j = 1; j < c->argc; j+=2) {
if (!strcasecmp(c->argv[j]->ptr,"listening-port")) {
long port;
if ((getLongFromObjectOrReply(c,c->argv[j+1],
&port,NULL) != C_OK))
return;
c->slave_listening_port = port;
} else if (!strcasecmp(c->argv[j]->ptr,"ip-address")) {
sds ip = c->argv[j+1]->ptr;
if (sdslen(ip) < sizeof(c->slave_ip)) {
memcpy(c->slave_ip,ip,sdslen(ip)+1);
} else {
addReplyErrorFormat(c,"REPLCONF ip-address provided by "
"replica instance is too long: %zd bytes", sdslen(ip));
return;
}
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
/* Ignore capabilities not understood by this master. */
if (!strcasecmp(c->argv[j+1]->ptr,"eof"))
c->slave_capa |= SLAVE_CAPA_EOF;
else if (!strcasecmp(c->argv[j+1]->ptr,"psync2"))
c->slave_capa |= SLAVE_CAPA_PSYNC2;
} else if (!strcasecmp(c->argv[j]->ptr,"ack")) {
/* REPLCONF ACK用来向master报告复制偏移量。这个内部命令仅仅被用于slave。 */
long long offset;
if (!(c->flags & CLIENT_SLAVE)) return;
if ((getLongLongFromObject(c->argv[j+1], &offset) != C_OK))
return;
if (offset > c->repl_ack_off)
c->repl_ack_off = offset;
c->repl_ack_time = server.unixtime;
/* 如果是无盘复制,当接收到第一个ACK时,我们需要让slave真正在线 */
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
putSlaveOnline(c);
/* 这个命令什么都不会返回 */
return;
} else if (!strcasecmp(c->argv[j]->ptr,"getack")) {
/* REPLCONF GETACK is used in order to request an ACK ASAP
* to the slave. */
if (server.masterhost && server.master) replicationSendAck();
return;
} else {
addReplyErrorFormat(c,"Unrecognized REPLCONF option: %s",
(char*)c->argv[j]->ptr);
return;
}
}
addReply(c,shared.ok);
}
addReply实现
/* 把robj对象的字符串表示写入到客户端的output buffer */
void addReply(client *c, robj *obj) {
if (prepareClientToWrite(c) != C_OK) return;
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
_addReplyStringToList(c,obj->ptr,sdslen(obj->ptr));
} else if (obj->encoding == OBJ_ENCODING_INT) {
/* For integer encoded strings we just convert it into a string
* using our optimized function, and attach the resulting string
* to the output buffer. */
char buf[32];
size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
if (_addReplyToBuffer(c,buf,len) != C_OK)
_addReplyStringToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}
/* 当我们每次想往客户端传输数据时,这个函数都会被调用.
*
* 如果客户端(正常的客户端)应该接受新的数据,这个函数将会返回C_OK,并且确保当socket可写时
* 在AE事件循环中安装好命令回复处理器.
*
* 当客户端不应该接收一个数据时,比如fake客户端(比如用来在内存中做AOF的),作为主服务器
* 或者安装命令回复处理器失败时,将会返回C_ERR.
*
* 这个函数在以下两种情况下不会安装命令回复处理器就回复C_OK:
* 1) 当命令回复处理器已经被安装了;
* 2) 当客户端是从服务器但是不在线,我们想要加速写,所以不确保会发送数据.
*
* 典型的,这个函数将会在reply准备好,在写入客户端的回复缓冲区前调用。
* 如果没有数据应该增加,将会返回ERR
*/
int prepareClientToWrite(client *c) {
/* Lua客户端和Module客户端一定需要获得数据,但是我们不安装命令回复器,因为没有socket */
if (c->flags & (CLIENT_LUA|CLIENT_MODULE)) return C_OK;
/* CLIENT REPLY OFF / SKIP handling: don't send replies. */
if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;
/* master不接受reply,除非设置了CLIENT_MASTER_FORCE_REPLY标志 */
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
if (c->fd <= 0) return C_ERR; /* AOF加载时的Fake客户端 */
/* 如果当前已经安装了-输出缓冲区中仍然有数据,则不再重复安装 */
if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c);
/* Authorize the caller to queue in the output buffer of this client. */
return C_OK;
}
1. 创建一个maven项目
2. 创建com.CoberturaStart.java
package com;
public class CoberturaStart {
public void helloEveryone(){
System.out.println("=================================================
我并不知道出现这个问题的实际原理,只是通过其他朋友的博客,文章得知的一个解决方案,目前先记录一个解决方法,未来要是真了解以后,还会继续补全.
在mysql5中有一个safe update mode,这个模式让sql操作更加安全,据说要求有where条件,防止全表更新操作.如果必须要进行全表操作,我们可以执行
SET
public class DeleteSpecificChars {
/**
* Q 63 在字符串中删除特定的字符
* 输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。
* 例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”
*/
public static voi
File descriptors are represented by the C int type. Not using a special type is often considered odd, but is, historically, the Unix way. Each Linux process has a maximum number of files th