redis cluster系统分析

redis服务的一些注意事项

  • server.client_max_querybuf_len redis:写入时,如果数据长度大于该配置,则redis会关闭并释放链接,该参数默认为1G。
  • server.maxidletime:在redis的定时器时间中,会对每个客户端的最近一次请求时间和当前时间进行比较,如果超过了该参数,则认为idle过长时间,redis会关闭并释放链接。该参数的限制会使一些执行时间过长的命令出现问题,特别像multi、exec命令,有可能执行时间过长,出现相关问题。
  • server.client_obuf_limits:在redis给client发送数据的过程中,会根据该参数判断发送的数据量是否过大,如果满足如下条件,redis会关闭并释放链接。
    1)hard_limit_bytes不为0,且发送的数据大于hard_limit_bytes。
    2) soft_limit_bytes不为0,且发送的数据量大于soft_limit_bytes,同时,持续时间超过soft_limit_seconds。
  • redis 3.x的bug:迁移时会将不存在过期时间的记录设置过期时间,导致数据丢失。
 void migrateCommand(client *c) {
   ... ... 
   long long ttl, expireat;
   for (j = 0; j < num_keys; j++) {
        expireat = getExpire(c->db,kv[j]);
        if (expireat != -1) {
            ttl = expireat-mstime();
            if (ttl < 1) ttl = 1;
        }
    ... ...
    }
 }
如上所示,ttl在for循环外设置,如果某个key没有设置ttl,则在循环中迁移该key的时候会使用上一个获得的ttl。
  • hiredis客户端中的限制:redisAppendCommandArgv函数实现中存在2G限制的限制。
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) {
    int totlen, j;
    totlen = 1+intlen(argc)+2;
    for (j = 0; j < argc; j++) {
        len = argvlen ? argvlen[j] : strlen(argv[j]);
        totlen += bulklen(len);
    }
    /* Build the command at protocol level */
    cmd = malloc(totlen+1);
    ... ...
}
超过2G的长度的时候,totlen可能为负数,而malloc函数参数为uint32_t,因此可能会分配一个超大的内存导致客户端程序异常。
  • redis cluster集群的异常恢复
    备节点挂掉然后重启的场景下,如果待同步的数据超过master节点环形缓冲区管理的buffer后,slave节点会启动全量同步,但是svale节点启动加入集群后,集群马上对外暴露slave节点可读,如果客户端读取slave节点,则容易出现大量数据读取异常,如果启动后slave节点为数据增量同步,则也会有数据读取异常的影响,只是影响比全量同步的影响小。
    基于以上的分析,线上运营时一般会配置slave节点为冷备节点,平常不承载读,只会承载master的同步写,这样,相同的并发下,redis cluster需要的节点数会需要更多,带来集群规模的成倍增加,相应的,资源成本也会成倍增加。

redis cluster数据同步

数据全同步

Created with Raphaël 2.1.0 networking networking replication replication server server connectWithMaster syncWithMaster sendSynchronousCommand PING、AUTH、REPLCONF slaveTryPartialResynchronization sendSynchronousCommand PSYNC readQueryFromClient(收到psync命令请求) syncCommand masterTryPartialResynchronization startBgsaveForReplication() rdbSaveBackground createReplicationBacklog() serverCron() backgroundSave DoneHandler() backgroundSave DoneHandlerDisk() updateSlavesWaitingBgsave() sendBulkToSlave() putSlaveOnline() readSyncBulkPayload() emptyDb() rdbLoad() replicationCreateMasterClient()

数据全同步分析:

  1. 通过replicationSetMaster函数设置了自己的master节点后,在定时执行的replicationCron函数中会判断server.repl_state是否为REPL_STATE_CONNECT,如果是,则会调用connectWithMaster函数, 在该函数中为该fd添加事件处理函数syncWithMaster。
  2. 当步骤1添加事件后,当有事件发生时,会触发syncWithMaster回调函数,在syncWithMaster函数中,会依次发送一系列的命令,如PING、AUTH、REPLCONF。
  3. 上述命令发送ok,并且master节点正常响应后,会调用slaveTryPartialResynchronization函数,在该函数中会发送PSYNC命令,同时,会在发送的PSYNC命令中带上psync_runid跟psync_offset参数。

    如果有server.cached_master(slave节点调用freeClient函数释放链接时,会判断释放的是否为跟master节点的链接,如果是,则会调用replicationCacheMaster函数保存server.cached_master,并且重置server.repl_state为REPL_STATE_CONNECT),则会将psync_runid和psync_offset分别设置为server.cached_master中的replrunid跟reploff+1,如果没有server.cached_master(比如第一次连上master),则会将psync_runid和psync_offset分别设置为”?”跟”-1”。

  4. 当master节点收到slave节点发过来的PSYNC命令后,会调用syncCommand函数,在syncCommand函数中,判断命令为psync时,会调用函数masterTryPartialResynchronization,在函数masterTryPartialResynchronization中,会判断slave发送的psync命令中带的runid是否跟master节点本身的runid一致,如果一致,则会判断slave发送过来的psync_offset是否在当前master节点的repl_backlog范围之内,如果不在当前repl_backlog范围之内,则会进入全同步流程。

    master节点在启动的时候就会调用getRandomHexChars函数得到一个随机字符串设置为自己的server.runid,master节点判断slave节点需要FULLRESYNC时,master节点会将自己的server.runid和server.master_repl_offset发送给slave,slave收到FULLRESYNC响应时,会将server.repl_master_runid和server.repl_master_initial_offset分别设置为master发送过来的runid和offset,slave节点在调用replicationCreateMasterClient函数时,又会将server.master的reploff和replrunid分别设置为server.repl_master_initial_offset和server.repl_master_runid。

  5. 在master节点的全量同步的处理中,如果当前没有在dump rdb文件,则会调用startBgsaveForReplication函数fork一个进程去dump rdb文件,在serverCron函数中会判断dump rdb文件是否完成,如果完成了dump rdb文件,则会调用updateSlavesWaitingBgsave函数为每个slave注册AE_WRITABLE的网络事件,如果AE_WRITABLE事件触发,则调用sendBulkToSlave函数将rdb文件发送给slave。当发送完成后,会删除原有注册的AE_WRITABLE网络事件,重新为slave注册AE_WRITABLE网络事件和sendReplyToClient回调函数,注册该回调函数后,会将master的修改操作异步发送给slave节点。

数据部分同步

Created with Raphaël 2.1.0 networking networking replication replication server server connectWithMaster syncWithMaster sendSynchronousCommand PING、AUTH、REPLCONF slaveTryPartialResynchronization sendSynchronousCommand PSYNC readQueryFromClient(收到psync命令请求) syncCommand masterTryPartialResynchronization addReplyReplicationBacklog readQueryFromClient() processCommand() sendReplyToClient()

数据部分同步分析:

  1. 进入部分同步之前的流程与全同步流程步骤1 ~ 4类似,不同的是在步骤4中,master节点收到slave节点发过来的命令后,如果判断slave发送过来的命令中的psync_offset在当前master节点的repl_backlog范围之内,那么则会进入部分同步流程。
  2. 进入部分同步流程后,会调用addReplyReplicationBacklog函数将repl_backlog中从psync_offset开始的后续所有修改操作的数据发送给slave节点。

    1. repl_backlog_size: repl_backlog的大小。
    2. master_repl_offset: repl_backlog全局范围的last pos。
    3. repl_backlog_off:repl_backlog在全局pos中的start pos,repl_backlog_off = master_repl_offset - repl_backlog_histlen + 1
    4. repl_backlog_idx:repl_backlog在repl_backlog_size范围的last pos,这个是循环的,范围在0 ~ repl_backlog_size - 1。
    5. repl_backlog_histlen: repl_backlog的数据已写入大小,范围在0 ~ repl_backlog_size。
  3. addReplyReplicationBacklog执行时,会先通过psync_offset - repl_backlog_off计算得到repl_backlog中要跳过同步的数据大小skip。

  4. 计算要同步的内容在repl_backlog_size范围的start pos的变量j, j = ( repl_backlog_idx + (repl_backlog_size - repl_backlog_histlen) )% repl_backlog_size。
  5. 对j进行取余处理, j = (j + skip) % repl_backlog_size,然后,计算要同步的数据长度len = repl_backlog_histlen - skip。
  6. 最后,将repl_backlog中从j的pos处开始同步后续所有数据到slave。
    while(len) {
           long long thislen =
               ((server.repl_backlog_size - j) < len) ?
               (server.repl_backlog_size - j) : len;
    
           serverLog(LL_DEBUG, "[PSYNC] addReply() length: %lld", thislen);
           addReplySds(c,sdsnewlen(server.repl_backlog + j, thislen));
           len -= thislen;
           j = 0;
       }

主备复制

redis cluster采用的是异步复制,尽可能保证最终一致性,一致性较弱,因此,有可能出现一些极端异常场景,比如master接受到请求处理成功,返回给客户端执行成功,但是master未将要同步的数据发送给slave就挂了,然后slave主备切换为master,但是该条记录已经丢失,给业务层的感知即为数据丢失。

Created with Raphaël 2.1.0 networking networking server server replication replication putSlaveOnline() readQueryFromClient() processCommand() proc() propagate() replicationFeedSlaves() feedReplicationBacklogWithObject() addReplyMultiBulkLen(slave) addReplyBulk(slave) sendReplyToClient()

主备复制分析:


  1. 主节点在accept slave的连接后,会为该连接注册AE_WRITABLE网络写事件和对应的回调函数sendReplyToClient,当slave需要全同步时,会先删除之前注册的事件和回调函数,然后重新注册AE_WRITABLE写事件和对应的回调函数sendBulkToSlave,sendBulkToSlave回调函数将rdb文件同步到slave后,会删除注册的AE_WRITABLE网络事和对应的回调函数,然后调用putSlaveOnline重新为该slave的连接注册AE_WRITABLE事件和对应的回调函数sendReplyToClient。
  2. 客户端发送更新请求到master节点后, 会触发master节点的网络读事件,进而调用回调函数readQueryFromClient,读取请求后,会调用processCommand函数执行对应的命令更新mater节点的内存数据。
  3. master节点更新完本机的内存后,会调用propagate函数,并在函数中调用feedAppendOnlyFile函数将更新请求写入到aof文件和调用replicationFeedSlaves函数将更新请求写入到repl_backlog,同时,replicationFeedSlaves还会将更新请求同步到slave。


  1. 这里需要注意的是,实际主备同步并不是拿的repl_backlog中的数据进行同步的,而是在master节点中直接将更新操作拷贝到slave。
  2. slave节点收到master节点的更新操作的同步请求后,会调用readQueryFromClient函数读取请求,读取请求后,会将该slave与master的连接对应的reploffset增加读取的请求长度。

if (c->flags & CLIENT_MASTER) c->reploff += nread;

集群管理

Failover

  1. 集群所有节点启动后,同时通过自带ruby脚本给每节点都发送了meet节点消息中第一个节点的消息后,所有节点的server.cluster->nodes包含两个节点:节点本身、meet消息带过来的节点。
  2. clusterCron函数定期执行,与本节点已知的其他所有节点握手、发送心跳消息,每一次握手、心跳消息,都会附带已知的10%节点的状态信息。其他节点收到消息后,会更新或新增节点的状态信息。
  3. clusterCron函数除了定期与其他节点握手或心跳之外,还会根据本节点已知的状态信息处理备节点强制Failover、本节点Failover、设置集群分布。
Created with Raphaël 2.1.0 网络io 网络io clusterCron clusterCron clusterReadHandler clusterReadHandler handshake heartbeat 接收其他节点发送过来的 handshake或heartbeat消息 clusterProcessPacket 更新集群状态信息 根据集群状态,是否需要Failover, 如果需要Failover,则调用 clusterRequestFailoverAuth发起投票。 接收其他节点通过clusterRequestFailoverAuth发送过来 的CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息。 clusterSend FailoverAuthIfNeeded 发送投票响应 接受到投票响应 clusterFailover ReplaceYourMaster

redis cluster的Failover采用的是raft算法的多数派投票策略。
1. 投票请求只会发送给master节点,如果是slave节点或本节点本身没有承载slots,则节点不会发送投票响应消息。
2. 发起投票的节点的epoch号必要要大于投票节点,如果小于投票节点的epoch号,则投票节点不会发送投票响应。
3. 如果本节点已经投过票,则不会再给其他节点投票。
4. 如果发起投票节点为master,或者发起投票节点为slave,但是投票节点发现它的master为NULL,或者发现它的master节点没有fail,且force_ack为0,则不会发送响应。
5. 投票节点两次投票的时间间隔必须要大于2 * cluster_node_timeout。
6. 发起投票节点的epoch号必须要大于投票节点上承载的每一个slot的epoch号。

如果投票节点判断满足上述所有条件,则会发送跳票响应消息给发起投票节点,发起投票节点判断满足大多数条件,则会投票通过,执行Failover。

持久化

rdb持久化

触发条件

initServerConfig()初始化触发条件

appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ... ...
    for (j = 0; j < server.saveparamslen; j++) {
        struct saveparam *sp = server.saveparams+j;
        if (server.dirty >= sp->changes &&
            server.unixtime-server.lastsave > sp->seconds &&
            (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
            server.lastbgsave_status == C_OK))
            {
                rdbSaveBackground(server.rdb_filename);
                break;
            }
     }
     ... ...
}

flushall命令调用flushallCommand,清空整个数据库的数据,同时,将rdb文件也清空。

void flushallCommand(client *c) {
    signalFlushedDb(-1);
    server.dirty += emptyDb(NULL);
    addReply(c,shared.ok);
    if (server.rdb_child_pid != -1) {
        kill(server.rdb_child_pid,SIGUSR1);
        rdbRemoveTempFile(server.rdb_child_pid);
    }
    if (server.saveparamslen > 0) {
        int saved_dirty = server.dirty;
        rdbSave(server.rdb_filename);
        server.dirty = saved_dirty;
    }
    server.dirty++;
}

手动执行save命令,调用saveCommand()函数,此时服务端会阻塞, 直到rdb文件save完成。

void saveCommand(client *c) {
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    if (rdbSave(server.rdb_filename) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

prepareForShutdown触发

int prepareForShutdown(int flags) {
    ... ...
    if ((server.saveparamslen > 0 && !nosave) || save) {
        if (rdbSave(server.rdb_filename) != C_OK) {
            ... ...
            return C_ERR;
        }
    }
    ... ...
}

手动执行bgsave命令,调用bgsaveCommand函数触发。

void bgsaveCommand(client *c) {
   ... ...
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (server.aof_child_pid != -1) {
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
        } else {
           ... ...
        }
    } else if (rdbSaveBackground(server.rdb_filename) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

serverCron定时检查

if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
        server.rdb_bgsave_scheduled &&
        (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
         server.lastbgsave_status == C_OK))
    {
        if (rdbSaveBackground(server.rdb_filename) == C_OK)
            server.rdb_bgsave_scheduled = 0;
    }

slave全量同步master节点的数据,触发执行startBgsaveForReplication()函数。

void syncCommand(client *c) {
    ... ...
    startBgsaveForReplication(c->slave_capa);
    ... ...
}
void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
    ... ...
    if (startbgsave) startBgsaveForReplication(mincapa);
}
void replicationCron(void) {
    ... ...
    startBgsaveForReplication(mincapa);
}

aof持久化

propagate函数调用feedAppendOnlyFile函数,将更新操作命令添加到aof_buf中,后续处理中,会调用flushAppendOnlyFile函数将aof_buf中内容写入到aof文件中。

flushAppendOnlyFile调用时机

stopAppendOnly触发

void stopAppendOnly(void) {
    ... ...
    flushAppendOnlyFile(1);
    ... ...
}

serverCron定时处理

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ... ...
    if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);

    run_with_period(1000) {
        if (server.aof_last_write_status == C_ERR)
            flushAppendOnlyFile(0);
    }
    ... ...
}

如果是AOF_FSYNC_EVERYSEC刷盘方式,则会创建后台任务,由后台线程bioProcessBackgroundJobs执行。

void flushAppendOnlyFile(int force) {
    ... ...
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */
        server.aof_last_fsync = server.unixtime;
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && 
                   server.unixtime > server.aof_last_fsync)) {
        if (!sync_in_progress) aof_background_fsync(server.aof_fd);
        server.aof_last_fsync = server.unixtime;
    }
}

void aof_background_fsync(int fd) {
    bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}

在flushAppendOnlyFile函数写入aof_buf中数据时,发现AOF_FSYNC_EVERYSEC类型的fsync未将上次需要fsync的操作执行,则会设置aof_flush_postponed_start,并且,不是强制write数据到aof的话,如果持续未fsync的时间少于2s的情况下,不会写入数据到aof文件。持续时间超过2s,为避免过多数据堆积,会write数据到aof文件。

void flushAppendOnlyFile(int force) {
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
        sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;

    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                return;
            }
            server.aof_delayed_fsync++;
        }
    }
    ... ...
}

beforesleep中定期触发

aeSetBeforeSleepProc(server.el,beforeSleep);

void beforeSleep(struct aeEventLoop *eventLoop) {
    ... ...
    flushAppendOnlyFile(0);
    ... ...
}

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

rewrite机制

rewrite触发条件

bgrewriteaof命令

void bgrewriteaofCommand(client *c) {
    if (server.aof_child_pid != -1) {
        addReplyError(c,"Background append only file rewriting already in progress");
    } else if (server.rdb_child_pid != -1) {
        server.aof_rewrite_scheduled = 1;
        addReplyStatus(c,"Background append only file rewriting scheduled");
    } else if (rewriteAppendOnlyFileBackground() == C_OK) {
        addReplyStatus(c,"Background append only file rewriting started");
    } else {
        addReply(c,shared.err);
    }
}
  1. 如果aof_child_pid != -1,即rewrite进程正在进行aof rewrite,则直接返回。
  2. 如果rdb_child_pid != -1,即rdb进程正在进行rdb dump,则设置aof_rewrite_scheduled = 1,后续在serverCron中定时触发。
  3. 如果rewrite进程、rewrite进程均没有,则直接进行rewrite,并且设置aof_child_pid。

serverCron定时判断,aof_rewrite_scheduled触发

if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
    server.aof_rewrite_scheduled)
{
    rewriteAppendOnlyFileBackground();
}

serverCron中定时判断,容量触发

if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
    server.aof_rewrite_perc && server.aof_current_size > server.aof_rewrite_min_size)
{
        long long base = server.aof_rewrite_base_size ? server.aof_rewrite_base_size : 1;
        long long growth = (server.aof_current_size*100/base) - 100;
        if (growth >= server.aof_rewrite_perc) {
                ... ...
                rewriteAppendOnlyFileBackground();
        }
}
void flushAppendOnlyFile(int force) {
    ... ...
    server.aof_current_size += nwritten;
    ... ...
}

startAppendOnly

void configSetCommand(client *c) {
    ... ...
    config_set_special_field("appendonly") {
        int enable = yesnotoi(o->ptr);
        if (enable == -1) goto badfmt;
        if (enable == 0 && server.aof_state != AOF_OFF) {
            stopAppendOnly();
        } else if (enable && server.aof_state == AOF_OFF) {
            if (startAppendOnly() == C_ERR) {
                return;
            }
        }
    }
    ... ...
} 
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
    ... ...
    if (eof_reached) {
         if (server.aof_state != AOF_OFF) {
             int retry = 10;
             stopAppendOnly();
             while (retry-- && startAppendOnly() == C_ERR) {
                  sleep(1);
              }
              if (!retry) {
                  exit(1);
              }
          }
     }
     ... ...
}

startAppendOnly()

一般调用前会调用stopAppendOnly()函数,startAppendOnly()函数主要的处理是调用rewriteAppendOnlyFileBackground()函数将内存中全量数据以命令方式写入到aof文件,并且会将此过程中的其他变更命令也写入到aof文件,最后,后续所有的变更操作全部追加写入到aof文件中。

打开aof_filename文件,作为rewriteAppendOnlyFileBackground()函数启动子进程后,父进程写入更新命令的文件。

server.aof_last_fsync = server.unixtime;
server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);

调用函数rewriteAppendOnlyFileBackground()。

if (server.rdb_child_pid != -1) {
    server.aof_rewrite_scheduled = 1;
} else if (rewriteAppendOnlyFileBackground() == C_ERR) {
    close(server.aof_fd);
    return C_ERR;
}

rewriteAppendOnlyFileBackgroud()函数启动子进程dump命令文件

int rewriteAppendOnlyFileBackground(void) {
    ... ...
    if (aofCreatePipes() != C_OK) return C_ERR;
    if ((childpid = fork()) == 0) {
        ... ...
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            ... ...
            exitFromChild(0);
        } else {
            exitFromChild(1);
        }
    } else {
        /* Parent */
        ... ...
        server.aof_child_pid = childpid;
        ... ...
        return C_OK;
    }
    return C_OK; /* unreached */
}

rewriteAppendOnlyFileBackgroud()启动子进程dump命令文件时,此时,父进程如果有更新操作,会通过管道的方式发送给子进程,子进程收到管道中的命令后,会先读取,当连续读取超过1s时或者连续20ms管道中没有数据时,子进程会发送”!”给父进程,父进程收到后会停止写入管道,此时,父进程会将更新命令放在aof_rewrite_buf_blocks里,当父进程发现子进程结束时,会调用aofRewriteBufferWrite()函数将aof_rewrite_buf_blocks中的数据写入到命令文件中,并且发送消息通知子进程,子进程收到通知后,会将父进程最后写入管道的数据写入命令文件。

Created with Raphaël 2.1.0 Parent aof.c Parent aof.c Child aof.c Child aof.c Parent server.c Parent server.c rewriteAppendOnlyFileBackground fork() rewriteAppendOnlyFile(tmpfile) feedAppendOnlyFile aofRewriteBufferAppend() listAddNodeTail(aof_rewrite_buf_blocks) aeCreateFileEvent( aof_pipe_write_data_to_child aofChildWritfData) write(aof_pipe_write_data_to_child) aeWait(aof_pipe_read_data_from_parent) aofReadDiffFromParent() write(server.aof_pipe_write_data_to_child) write(server.aof_pipe_write_ack_to_parent,"!",1) aofChildPipeReadable() server.aof_stop_sending_diff = 1 write(server.aof_pipe_write_ack_to_child,"!",1) aeDeleteFileEvent (aof_pipe_read_ack_from_child) anetNonBlock(server.aof_pipe_read_ack_from_parent) syncRead(server.aof_pipe_read_ack_from_parent) aofReadDiffFromParent() rename(tmpfile,filename) wait3(&statloc,WNOHANG,NULL) backgroundRewriteDoneHandler() aofRewriteBufferWrite(filename) (for block in aof_rewrite_buf_blocks) { write(filename, block); } rename(filename,server.aof_filename) server.aof_fd = newfd

猜你喜欢

转载自blog.csdn.net/shangshengshi/article/details/78178072