rdb机制
rdb(redis database)是redis持久化方法中的一种,通过一次性将redis内存中的数据全量快照落盘,达到持久化的目的。全量落盘就意味着操作比较重度,无法做到快速秒级间隔持久化,所以一般用来做数据定时备份,例如每天一次。rdb的实现方法分为阻塞和非阻塞,阻塞就是执行快照期间不执行其它业务逻辑保证数据一致性,非阻塞就是fork子进程使用fork时候的主进程数据来落盘同时主进程还能对外提供服务。rdb的触发方式也分为手动和自动,手动就是redis客户端调用save或者bgsave,自动就是通过配置文件的save字段配置触发条件以及别的例如主从复制、flushall、shutdown时触发。
save
int rdbSave(int req, char *filename, rdbSaveInfo *rsi) {
char tmpfile[256];
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
FILE *fp = NULL;
rio rdb;
int error = 0;
char *err_op; /* For a detailed log */
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
char *str_err = strerror(errno);
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Failed opening the temp RDB file %s (in server root dir %s) "
"for saving: %s",
tmpfile,
cwdp ? cwdp : "unknown",
str_err);
return C_ERR;
}
rioInitWithFile(&rdb,fp);
startSaving(RDBFLAGS_NONE);
if (server.rdb_save_incremental_fsync)
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
if (rdbSaveRio(req,&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
errno = error;
err_op = "rdbSaveRio";
goto werr;
}
/* Make sure data will not remain on the OS's output buffers */
if (fflush(fp)) { err_op = "fflush"; goto werr; }
if (fsync(fileno(fp))) { err_op = "fsync"; goto werr; }
if (fclose(fp)) { fp = NULL; err_op = "fclose"; goto werr; }
fp = NULL;
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
if (rename(tmpfile,filename) == -1) {
char *str_err = strerror(errno);
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Error moving temp DB file %s on the final "
"destination %s (in server root dir %s): %s",
tmpfile,
filename,
cwdp ? cwdp : "unknown",
str_err);
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
if (fsyncFileDir(filename) == -1) { err_op = "fsyncFileDir"; goto werr; }
serverLog(LL_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
stopSaving(1);
return C_OK;
werr:
serverLog(LL_WARNING,"Write error saving DB on disk(%s): %s", err_op, strerror(errno));
if (fp) fclose(fp);
unlink(tmpfile);
stopSaving(0);
return C_ERR;
}
- 打开一个rdb临时文件
temp-<pid>.rdb
startSaving
,给事件模块发送一个开始rdb事件rdb_save_incremental_fsync
标记如果为true,rdb过程中每产生30MB数据就自动调用fsync落盘rdbSaveRio
开始执行rdb逻辑:-
rdbWriteRaw
写入rdb版本号,rdbSaveInfoAuxFields
写入一些辅助字段信息,rdbSaveModulesAux
遍历redis注册的模块写入模块的头辅助字段信息,rdbSaveFunctions
遍历所有注册的lua函数写入rdb,遍历dbnum的数据库执行rdbSaveDb
保存所有数据库数据(遍历每个key执行对应数据压缩保存),rdbSaveModulesAux
遍历redis注册的模块写入模块的尾辅助字段信息,写入RDB_OPCODE_EOF
结束标记,写入校验号 fflush
将文件流写入内核缓冲区fsync
将文件内容立刻写入磁盘fclose
关闭文件rename
重命名临时文件为正式rdb文件fsyncFileDir
同步目录数据到磁盘- rdb结束
bgsave
int rdbSaveBackground(int req, char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
if (hasActiveChildProcess()) return C_ERR;
server.stat_rdb_saves++;
server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL);
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
int retval;
/* Child */
redisSetProcTitle("redis-rdb-bgsave");
redisSetCpuAffinity(server.bgsave_cpulist);
retval = rdbSave(req, filename,rsi);
if (retval == C_OK) {
sendChildCowInfo(CHILD_INFO_TYPE_RDB_COW_SIZE, "RDB");
}
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
if (childpid == -1) {
server.lastbgsave_status = C_ERR;
serverLog(LL_WARNING,"Can't save in background: fork: %s",
strerror(errno));
return C_ERR;
}
serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
server.rdb_save_time_start = time(NULL);
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
return C_OK;
}
return C_OK; /* unreached */
}
hasActiveChildProcess
判断是否存在其它子进程,存在就不执行rdb- 调用fork,子进程开始rdb:
redisSetProcTitle
设置子进程名:redis-rdb-bgsaveredisSetCpuAffinity
设置cpu亲和性,将子进程绑定在某个cpu核心执行rdbSave
开始执行rdb,分析逻辑见上面sendChildCowInfo
给父进程管道发送rdb结束消息exitFromChild
退出子进程