适合人群
有一定Redis基础,想对Redis的持久化做深入了解的。
前言
阅读本文可以深入理解Redis持久化,本章不会对持久化概念做过多介绍,而是深入实现原理。
定义
持久化 : 我们知道redis是内存数据库,所有的数据都存储在内存中,如果服务器意外宕机或者服务器主进程意外退出,这时我们的数据会丢失。redis为了解决这个问题,提供了持久化功能,来避免这种数据丢失。
一、RDB持久化
rdb持久化是生成一个二进制文件。
触发rdb持久化时机
1.手动执行。 ??通过save 命令和 bgsave 命令,两个命令的区别是 save 命令阻塞主进程而bgsave命令是通过fork()函数创建出来的子进程进行持久化,不阻塞主进程。无论是save命令还是bgsave命令,最终都是执行服务器的rdbSave()函数,下面会讲解save和bgsave命令都干了些什么事。 2. 通过服务器配置选项定期执行。 通过redis.conf配置文件的save配置来检测什么时候触发rdb持久化。
save 900 1 // 900秒内至少有1个key改变
save 300 10 // 300秒内至少有10个key改变
save 60 10000 // 60秒内至少有10000个key改变
下面是检测代码 :
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, NULL);
break;
}
}
dirty 属性含义 : 服务器对多少key进行了改变,每次rdb持久化之后都会重新计算。 lastsave 属性含义 : 上次进行rdb持久化的时间 通过代码可以看出,服务器改变了1个key,并且时间过去了900秒,那么执行rdbSaveBackground()函数。
server.unixtime - server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK)
这里留个问题 : dirty 属性每次服务器执行key的改变都会进行增加,这个值是在什么时候清空的,因为主线程和bgsave是同时进行的? a. 猜测一 : 如果在执行rdbSaveBackground()函数同时清空dirty属性,那么rdbSaveBackground()函数执行失败如何恢复dirty属性。 b. 猜测二 : 在rdbSaveBackground()函数执行后,通过通知主线程来计算dirty这个属性该赋值为多少,dirty属性值不一定赋值为0,dirty属性值取决于在rdbSaveBackground()期间进行了多少key的改变。 提前透漏下,猜测二是正确的~.~。
save命令的执行过程
下面的代码对一些无用的代码进行了删减,不影响正确理解功能的实现
void saveCommand(client *c) {
if (server.rdb_child_pid != -1) {
return;
}
rdbSave(server.rdb_filename, NULL);
}
bgsave命令的执行过程
void bgsaveCommand(client *c) {
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
return;
}
rdbSaveBackground(server.rdb_filename, NULL)
}
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL);
if ((childpid = fork()) == 0) {
rdbSave(filename, rsi);
} else {
if (childpid == -1) {
server.lastbgsave_status = C_ERR;
return C_ERR;
}
server.rdb_child_pid = childpid;
return C_OK;
}
return C_OK;
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
}
}
}
}
void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
server.rdb_child_pid = -1;
}
int rdbSave(char *filename, rdbSaveInfo *rsi) {
char tmpfile[256];
FILE *fp;
rio rdb;
snprintf(tmpfile, 256, "temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile, "w");
rdbSaveRio(&rdb, &error, RDB_SAVE_NONE, rsi);
rename(tmpfile,filename);
server.dirty = 0;
server.lastsave = time(NULL);
server.lastbgsave_status = C_OK;
return C_OK;
}
rdb总结 :
1.save命令是同步调用rdbsave()函数进行持久化的。 2.bgsave命令或者定时任务检测save配置持久化,都是通过调用rdbSaveBackground()函数,fork子进程进行异步处理。子进程处理完后,主进程的定时任务serverCron()处理函数会检测到,调用backgroundSaveDoneHandlerDisk()函数进行数据初始化。 3.bgsave schedule 命令如果正在执行aof文件重写,那么会在aof重写完成后进行rdb持久化操作,这个不断检测是在定时任务serverCron()函数中进行的。上面没有讲解,是因为不想代码太过于复杂,难以理解。
二、AOF持久化
aof持久化是将每个redis的写命令都保存在aof文件里,aof文件的所有命令都是以redis命令的请求协议(resp协议)格式保存的纯文本文件。aof持久化分为命令追加、文件写入、文件同步。 命令追加 : 每次redis成功执行写命令后,会把命令按照请求协议(resp协议)放入到aof缓冲区中。
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
buf = "生成resp协议的命令";
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf, buf, sdslen(buf));
if (server.aof_child_pid != -1)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
sdsfree(buf);
}
文件写入 : 每次在执行命令前,会把aof缓冲区中的数据写到文件(操作系统会把这个数据保存到内存缓冲区)。 文件同步 : 根据appendfsync配置的值,判断什么时候把操作系统的内存缓冲区的文件数据同步到磁盘文件里。
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
if (sdslen(server.aof_buf) == 0) return;
nwritten = write(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
server.aof_current_size += nwritten;
if (server.aof_no_fsync_on_rewrite && (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
return;
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
aof_fsync(server.aof_fd);
server.aof_last_fsync = server.unixtime;
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && server.unixtime > server.aof_last_fsync)) {
aof_background_fsync(server.aof_fd);
server.aof_last_fsync = server.unixtime;
}
}
三、AOF重写
触发aof重写时机
1.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 {
rewriteAppendOnlyFileBackground()
}
}
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && server.aof_rewrite_scheduled) {
rewriteAppendOnlyFileBackground();
}
2.ServerCron()函数定期检测是否需要进行aof重写
if (server.rdb_child_pid == -1
&& server.aof_child_pid == -1
&& 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();
}
}
对于aof重写,最终都会执行rewriteAppendOnlyFileBackground()函数,生成aof临时文件。 ServerCron()函数会检测aof重写完成后,执行aof回调函数backgroundRewriteDoneHandler()。
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
}
}
}
}
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
snprintf(tmpfile,256, "temp-rewriteaof-bg-%d.aof", (int)server.aof_child_pid);
int newfd = open(tmpfile,O_WRONLY|O_APPEND);
aofRewriteBufferWrite(newfd)
rename(tmpfile, server.aof_filename)
}
四、混合持久化
redis 4.0版本之后支持混合持久化。通过redis.conf配置文件配置aof-use-rdb-preamble为yes来开启混合持久化。
int rewriteAppendOnlyFile(char *filename) {
rio aof;
char tmpfile[256];
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
FILE *fp = fopen(tmpfile,"w");
if (server.aof_use_rdb_preamble) {
rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL);
} else {
rewriteAppendOnlyFileRio(&aof);
}
return C_OK;
}
总结
|