这里只介绍rdb是在什么时候触发的,rdb本身的数据格式不会被介绍,通过观察代码大体可以发现,就是按照约定的格式对redisObject进行加工,或者按照解析将数据流还原成redisObject。有关rdb的加载逻辑在redis的初始化流程中已经有介绍了。现在就来看看什么时候会触发rdb的生成。
首先在有关aof的解析中可以看到。对应瘦身aof文件的生成,根据server的配置项会选择先生成rdb数据。
对应的代码为
int rewriteAppendOnlyFile(char *filename) {
...
// 代表该aof文件还存储了rdb格式的数据 这种aof文件恢复数据会更快
if (server.aof_use_rdb_preamble) {
int error;
// 先将快照数据存储到rdb文件中 当作为aof生成rdb数据时 rdbflags是 RDBFLAGS_AOF_PREAMBLE 这样在生成rdb数据的同时 还会抽空从父进程中读取后写入的aof数据
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
errno = error;
goto werr;
}
} else {
// 基于当前db的数据 直接产生最简的command 相当于瘦身后的aof
if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
}
复制代码
之后再来看看还有哪些动作会触发rdb文件的生成,可以发现redis对外暴露2个command
saveCommand:
/**
* 执行save命令 就是基于当前数据生成快照(rdb)
* @param c
*/
void saveCommand(client *c) {
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
return;
}
rdbSaveInfo rsi, *rsiptr;
// 根据当前服务器信息填充 rdbSaveInfo
rsiptr = rdbPopulateSaveInfo(&rsi);
// 根据保存结果返回信息到client
if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
addReply(c,shared.ok);
} else {
addReply(c,shared.err);
}
}
复制代码
rdbSave()这个方法就不详细展开了,就是挨个处理每个db下的redisObject对象,将它们按照rdb协议规定的格式写入到文件中,在转换过程中往往会涉及到压缩算法或者一些编码技巧,用于节省内存,这个套路跟lucene一致。 除此之外在准备关闭redis服务器时也会尝试生成rdb文件。
/**
* 准备关闭redis服务器
* @param flags
* @return
*/
int prepareForShutdown(int flags) {
...
/* Create a new RDB file before exiting.
* 如果强制要求了save 就会生成一份rdb数据
* 如果没有强制要求save 就会检测此时是否满足saveparam条件
* 如果要求了nosave 就必然不会生成rdb数据
* */
if ((server.saveparamslen > 0 && !nosave) || save) {
serverLog(LL_NOTICE,"Saving the final RDB snapshot before exiting.");
// 打印一些监督信息 先忽略
if (server.supervised_mode == SUPERVISED_SYSTEMD)
redisCommunicateSystemd("STATUS=Saving the final RDB snapshot\n");
/* Snapshotting. Perform a SYNC SAVE and exit */
rdbSaveInfo rsi, *rsiptr;
// 从server上获取一些信息 并填充到rdbSaveInfo中
rsiptr = rdbPopulateSaveInfo(&rsi);
// 生成快照数据 并保存到rdb文件中
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
/* Ooops.. error saving! The best we can do is to continue
* operating. Note that if there was a background saving process,
* in the next cron() Redis will be notified that the background
* saving aborted, handling special stuff like slaves pending for
* synchronization... */
复制代码
之后还有使用子进程生成rdb数据的api--bgsaveCommand
/* BGSAVE [SCHEDULE]
* 使用子进程执行rdb生成任务
* */
void bgsaveCommand(client *c) {
int schedule = 0;
/* The SCHEDULE option changes the behavior of BGSAVE when an AOF rewrite
* is in progress. Instead of returning an error a BGSAVE gets scheduled.
* 如果携带了其他参数 检查是否开启了定时选项
* */
if (c->argc > 1) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
schedule = 1;
} else {
addReply(c,shared.syntaxerr);
return;
}
}
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
// 此时已经开启了后台进程 无法继续创建
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
// 此时有其他子进程正在运行 如果本次允许延迟处理 返回延迟处理的信息 否则返回失败的信息
} else if (hasActiveChildProcess()) {
if (schedule) {
server.rdb_bgsave_scheduled = 1;
addReplyStatus(c,"Background saving scheduled");
} else {
addReplyError(c,
"Another child process is active (AOF?): can't BGSAVE right now. "
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
"possible.");
}
// 使用后台线程生成rdb
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
}
复制代码
server.rdb_bgsave_scheduled这个标记的作用和那个延迟执行aof的标记作用一致,也是在serverCron的主循环中发现该标记,并开启子进程生成rdb。
然后在serverCron中还会根据saveparam参数尝试执行rdb操作
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now.
* 此时没有正在执行的后台任务,判断是否满足saveparam条件 并触发存储rdb逻辑
* */
for (j = 0; j < server.saveparamslen; j++) {
// 遍历所有保存条件
struct saveparam *sp = server.saveparams+j;
/* Save if we reached the given amount of changes,
* the given amount of seconds, and if the latest bgsave was
* successful or if, in case of an error, at least
* CONFIG_BGSAVE_RETRY_DELAY seconds already elapsed.
* 只要满足任意一个保存条件 即触发rdb生成
* */
if (server.dirty >= sp->changes &&
server.unixtime-server.lastsave > sp->seconds &&
// 如果上一次生成rdb是成功的 那么本次也可以继续尝试生成rdb 如果上一次生成rdb是失败的 那么会有一个等待时间 必须间隔这么长时间后才能生成rdb
(server.unixtime-server.lastbgsave_try >
CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK))
{
serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
sp->changes, (int)sp->seconds);
rdbSaveInfo rsi, *rsiptr;
// 通过db信息填充saveInfo
rsiptr = rdbPopulateSaveInfo(&rsi);
rdbSaveBackground(server.rdb_filename,rsiptr);
break;
}
}
复制代码
而rdbSaveBackground的核心逻辑就是使用子进程执行rdbSave,这样就和前面关联起来了。 最后当子进程完成任务时,根据触发rdb的任务类型执行不同的后置函数
/* When a background RDB saving/transfer terminates, call the right handler.
* rdb子进程被完全回收时执行该方法
* */
void backgroundSaveDoneHandler(int exitcode, int bysignal) {
int type = server.rdb_child_type;
switch(server.rdb_child_type) {
case RDB_CHILD_TYPE_DISK:
// 当负责将rdb数据写入到文件的子进程完成工作后 触发该方法 判断子进程是否被意外关闭 并清理临时文件
backgroundSaveDoneHandlerDisk(exitcode,bysignal);
break;
// 当slave需要同步数据时 产生的rdb子进程类型就是socket类型
case RDB_CHILD_TYPE_SOCKET:
// 这里主要就是打印日志
backgroundSaveDoneHandlerSocket(exitcode,bysignal);
break;
default:
serverPanic("Unknown RDB child type.");
break;
}
// 重置子进程状态 同时更新存储时间
server.rdb_child_pid = -1;
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
server.rdb_save_time_start = -1;
/* 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);
}
/* A background saving child (BGSAVE) terminated its work. Handle this.
* This function covers the case of actual BGSAVEs.
* 某个后台存储rdb文件的任务已经完成 这时清理逻辑
* */
static void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
// 代表本次处理成功
if (!bysignal && exitcode == 0) {
serverLog(LL_NOTICE,
"Background saving terminated with success");
// 此时dirty的数值就更新成 仅生成rdb文件期间产生的数据
server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
} else if (!bysignal && exitcode != 0) {
serverLog(LL_WARNING, "Background saving error");
server.lastbgsave_status = C_ERR;
} else {
// 代表本次进程由于某种信号被终止
mstime_t latency;
serverLog(LL_WARNING,
"Background saving terminated by signal %d", bysignal);
latencyStartMonitor(latency);
// 清理子进程创建的临时文件
rdbRemoveTempFile(server.rdb_child_pid, 0);
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
* triggering an error condition. */
if (bysignal != SIGUSR1)
server.lastbgsave_status = C_ERR;
}
}
复制代码
以上rdb的解析也完成了