公众号推荐
微信公众号搜"智元新知" 关注 微信扫一扫可直接关注哦!
loadAppendOnlyFile( *filename) {
redis Client *fakeClient;
*fp = fopen(filename, );
redis _stat sb;
old_aof_state = server.aof_state;
loops = 0;
(fp && redis _fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
server.aof_current_size = 0;
fclose(fp);
REdis _ERR;
}
(fp == NULL) {
redis Log(REdis _WARNING,%s" ,strerror(errno));
exit(1);
}
server.aof_state = REdis _AOF_OFF;
fakeClient = createFakeClient();
startLoading(fp);
(1) {
argc, j;
unsigned len;
robj **argv;
buf[128];
sds argsds;
redis Command *cmd;
(!(loops++ % 1000)) {
loadingProgress(ftello(fp));
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
}
(fget s(buf, (buf),fp) == NULL) {
(feof(fp))
;
readerr;
}
(buf[0] != ) fmterr;
argc = atoi(buf+1);
(argc < 1) fmterr;
argv = zmalloc((robj*)*argc);
(j = 0; j < argc; j++) {
(fget s(buf,fp) == NULL) readerr;
(buf[0] != ) fmterr;
len = strtol(buf+1,NULL,10);
argsds = sdsnewlen(NULL,len);
(len && fread(argsds,len,1,fp) == 0) fmterr;
argv[j] = createObject(REdis _STRING,argsds);
(fread(buf,2,fp) == 0) fmterr;
}
cmd = lookupCommand(argv[0]->ptr);
(!cmd) {
redis Log(REdis _WARNING,Nown command '%s ' reading the append only file" , ( *)argv[0]->ptr);
exit(1);
}
fakeClient->argc = argc;
fakeClient->argv = argv;
cmd->proc(fakeClient);
redis Assert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
redis Assert((fakeClient->flags & REdis _BLOCKED) == 0);
(j = 0; j < fakeClient->argc; j++)
decrRefCount(fakeClient->argv[j]);
zfree(fakeClient->argv);
}
(fakeClient->flags & REdis _MULTI) readerr;
fclose(fp);
freeFakeClient(fakeClient);
server.aof_state = old_aof_state;
stopLoading();
aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size;
REdis _OK;
…………
}
在前面一篇关于AOF参数配置的博客 遗留了一个 问题,server.aof_current_size参数的初始化,下面解决 这个疑问。
<div class="dp-Highlighter bgcpp">
<div class="bar">
<div class="tools">
@H 404 _151@[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">copy <a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">print <a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"> ;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074 " target="_blank">
<img src="https://code.csdn.net/assets/CODE_ico.png " alt="在CODE上查看代码片" width="12" height="12">
<a title="派生到我的代码片" href="
https://code.csdn.net/snippets/152074/fork " target="_blank">
<img src="https://code.csdn.net/assets/ico_fork.svg " alt="派生到我的代码片" width="12" height="12">
aofUpdateCurrentSize( ) {
redis _stat sb;
(redis _fstat(server.aof_fd,&sb) == -1) {
redis Log(REdis _WARNING,%s" ,
strerror(errno));
} {
server.aof_current_size = sb.st_size;
}
}
redis _fstat是作者对Linux中fstat64函数 的重命名 ,该还是就是获取 文件 相关的参数信息,具体可以Google之,sb.st_size就是当前AOF文件 的大小。这里需要知道server.aof_fd即AOF文件 描述符,该参数的初始化在initServer()函数 中
<div class="dp-Highlighter bgcpp">
<div class="bar">
<div class="tools">
@H 404 _151@[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">copy <a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">print <a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"> ;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074 " target="_blank">
<img src="https://code.csdn.net/assets/CODE_ico.png " alt="在CODE上查看代码片" width="12" height="12">
<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork " target="_blank"><img src="https://code.csdn.net/assets/ico_fork.svg " alt="派生到我的代码片" width="12" height="12">
(server.aof_state == REdis _AOF_ON) {
server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
(server.aof_fd == -1) {
redis Log(REdis _WARNING, %s" ,strerror(errno));
exit(1);
}
}
至此,Redis Server启动加载硬盘中AOF文件 数据的操作就成功结束了。
当客户端执行Set等修改数据库中字段的指令时就会造成Server数据库中数据被修改,这些修改的数据应该被实时更新到AOF文件中,并且也要按照一定的fsync机制刷新到硬盘中,保证数据不会丢失。
在上一篇博客中,提到了三种fsync方式:appendfsync always, appendfsync everysec, appendfsync no. 具体体现在server.aof_fsync参数中。
首先看当客户端请求的指令造成数据被修改,Redis是如何将修改数据的指令添加到server.aof_buf中的。
call() -> propagate() -> feedAppendOnlyFile(),call()函数判断执行指令后是否造成数据被修改。
feedAppendOnlyFile函数首先会判断Server是否开启了AOF,如果开启AOF,那么根据Redis通讯协议将修改数据的指令重现成请求的字符串,注意在超时设置的处理方式,接着将字符串append到server.aof_buf中即可。该函数最后两行代码需要注意,这才是重点,如果server.aof_child_pid != -1那么表明此时Server正在重写rewrite AOF文件,需要将被修改的数据追加到server.aof_rewrite_buf_blocks链表中,等待rewrite结束后,追加到AOF文件中。具体见下面代码的注释。
<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">copy <a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">print <a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"> ;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074 " target="_blank">
<img src="https://code.csdn.net/assets/CODE_ico.png " alt="在CODE上查看代码片" width="12" height="12">
<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork " target="_blank"><img src="https://code.csdn.net/assets/ico_fork.svg " alt="派生到我的代码片" width="12" height="12">
propagate( redis Command *cmd, dbid, robj **argv, argc,
flags)
{
(server.aof_state != REdis _AOF_OFF && flags & REdis _PROPAGATE_AOF)
Feed AppendOnlyFile(cmd,dbid,argv,argc);
(flags & REdis _PROPAGATE_REPL)
replicationFeed Slaves(server.slaves,argc);
}
Highlighter bg_cpp">
404_151@[cpp]
Feed AppendOnlyFile( redis Command *cmd, dictid, argc) {
sds buf = sdsempty();
robj *tmpargv[3];
(dictid != server.aof_selected_db) {
seldb[64];
snprintf(seldb,(seldb), ,dictid);
buf = sdscatprintf(buf,%s\r\n" ,
(unsigned )strlen(seldb),seldb);
server.aof_selected_db = dictid;
}
(cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == expireatCommand) {
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
}
(cmd->proc == setexCommand || cmd->proc == psetexCommand) {
tmpargv[0] = createStringObject(,3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf,argv[2]);
} {
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
(server.aof_state == REdis _AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
(server.aof_child_pid != -1)
aofRewriteBufferAppend((unsigned *)buf,sdslen(buf));
sdsfree(buf);
}
Server在每次事件循环之前会调用 一次beforeSleep函数 ,下面看看这个函数 做了什么工作?
<div class="dp-Highlighter bgcpp">
<div class="bar">
<div class="tools">
@H 404 _151@[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">copy <a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">print <a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"> ;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074 " target="_blank">
<img src="https://code.csdn.net/assets/CODE_ico.png " alt="在CODE上查看代码片" width="12" height="12">
<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork " target="_blank"><img src="https://code.csdn.net/assets/ico_fork.svg " alt="派生到我的代码片" width="12" height="12">
beforeSleep( aeEventLoop *eventLoop) {
REdis _NOTUSED(eventLoop);
listNode *ln;
redis Client *c;
(server.active_expire_enabled && server.masterhost == NULL)
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
(listLength(server.unblocked_clients)) {
ln = listFirs t(server.unblocked_clients);
redis Assert(ln != NULL);
c = ln->value;
listDelNode(server.unblocked_clients,ln);
c->flags &= ~REdis _UNBLOCKED;
(c->querybuf && sdslen(c->querybuf) > 0) {
server.current_client = c;
processInputBuffer(c);
server.current_client = NULL;
}
}
flushAppendOnlyFile(0);
}
通过上面的代码 及注释可以发现,beforeSleep函数 做了三件事:1、处理过期键,2、处理阻塞期间的 客户端请求,3、将server.aof_buf中的数据追加到AOF文件 中并fsync刷新到硬盘上,flushAppendOnlyFile函数 给定了一个 参数force,表示是否强制写入AOF文件 ,0表示非强制即支持 延迟写,1表示强制写入。
<div class="dp-Highlighter bgcpp">
<div class="bar">
<div class="tools">
@H 404 _151@[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">copy <a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903">print <a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"> ;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074 " target="_blank">
<img src="https://code.csdn.net/assets/CODE_ico.png " alt="在CODE上查看代码片" width="12" height="12">
<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork " target="_blank"><img src="https://code.csdn.net/assets/ico_fork.svg " alt="派生到我的代码片" width="12" height="12">
flushAppendOnlyFile( force) {
ssize_t nwritten;
sync_in_progress = 0;
(sdslen(server.aof_buf) == 0) ;
(server.aof_fsync == AOF_FSYNC_EVERYSEC)
sync_in_progress = bioPendingJobsOfType(REdis _BIO_AOF_FSYNC) != 0;
(server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
(sync_in_progress) {
(server.aof_flush_postponed_start == 0) {
server.aof_flush_postponed_start = server.unixtime;
;
} (server.unixtime - server.aof_flush_postponed_start < 2) {
;
}
server.aof_delayed_fsync++;
redis Log(REdis _NOTICE,disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis ." );
}
}
server.aof_flush_postponed_start = 0;
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
(nwritten != ( )sdslen(server.aof_buf)) {
(nwritten == -1) {
redis Log(REdis _WARNING,%s" ,strerror(errno));
} {
redis Log(REdis _WARNING,
%s (nwritten=%ld, "
,
strerror(errno),
()nwritten,
()sdslen(server.aof_buf));
(ftrunc ate(server.aof_fd, server.aof_current_size) == -1) {
redis Log(REdis _WARNING, Could not remove short write "
dis may refuse "
runcate: %s " , strerror(errno));
}
}
exit(1);
}
server.aof_current_size += nwritten;
((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
sdsclear(server.aof_buf);
} {
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
(server.aof_no_fsync_on_rewrite &&
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
;
(server.aof_fsync == AOF_FSYNC_ALWAYS) {
aof_fsync(server.aof_fd);
server.aof_last_fsync = server.unixtime;
} ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
(!sync_in_progress) aof_background_fsync(server.aof_fd);
server.aof_last_fsync = server.unixtime;
}
}
上述代码 中请关注server.aof_fsync参数,即设置Redis fsync AOF文件 到硬盘的策略,如果设置为AOF_FSYNC_ALWAYS,那么直接在主进程中fsync,如果设置为AOF_FSYNC_EVERYSEC,那么放入后台 线程中fsync,后台 线程的代码 在bio.c中。
文章写到这,已经解决的了Redis Server启动加载AOF文件和如何将客户端请求产生的新的数据追加到AOF文件中,对于追加数据到AOF文件中,根据fsync的配置策略如何将写入到AOF文件中的新数据刷新到硬盘中,直接在主进程中fsync或是在后台线程fsync。
至此,AOF数据持久化还剩下如何rewrite AOF,接受客户端发送的BGREWRITEAOF请求,此部分内容待下篇博客中解析。
感谢此篇博客给我在理解Redis AOF数据持久化方面的巨大帮助,
本人Redis -2.8.2的源码注释已经放到Github中,有需要的读者可以下载,我也会在后续的时间中更新,
本人不怎么会使用Git,望有人能教我一下。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
本文所引用的源码全部来自Redis2.8.2版本。
Redis AOF数据持久化机制的实现相关代码是redis.c,config.c
在阅读本文之前请先阅读Redis数据持久化机制AOF原理分析之配置详解文章,了解AOF相关参数的解析,文章链接
接着上一篇 文章 ,本文将介绍Redis 是如何实现AOF rewrite的。
转载请注明,文章 出自
如果Redis 只是将客户端修改 数据库 的指令重现存储在AOF文件 中,那么AOF文件 的大小会不断的增加 ,因为AOF文件 只是简单的重现存储了客户端的指令,而并没有进行合并。对于该问题最简单的处理方式,即当AOF文件 满足一定条件时就对AOF进行rewrite,rewrite是根据当前内存数据库 中的数据进行遍历写到一个 临时的AOF文件 ,待写完后替换掉原来的AOF文件 即可。
Redis 触发AOF rewrite机制有三种:
1、Redis Server接收到客户端发送的BGREWRITEAOF指令请求,如果当前AOF/RDB数据持久化没有在执行,那么执行,反之,等当前AOF/RDB数据持久化结束后执行 AOF rewrite
2、在Redis 配置文件 redis .conf中,用户 设置了auto-aof-rewrite-percentage和auto-aof-rewrite-min-size参数,并且当前AOF文件 大小server.aof_current_size大于auto-aof-rewrite-min-size(server.aof_rewrite_min_size),同时AOF文件 大小的增长率大于auto-aof-rewrite-percentage(server.aof_rewrite_perc)时,会自动 触发AOF rewrite
3、用户 设置“config set appendonly yes”开启AOF的时,调用 startAppendOnly函数 会触发rewrite
下面分别介绍上述三种机制的处理.
[cpp]
> bgrewriteaofCommand(redis Client *c) {
(server.aof_child_pid != -1) {
addReplyError(c,rewriting already in progress" );
} (server.rdb_child_pid != -1) {
server.aof_rewrite_scheduled = 1;
addReplyStatus(c,rewriting scheduled" );
} (rewriteAppendOnlyFileBackground() == REdis _OK) {
addReplyStatus(c,rewriting started" );
} {
addReply(c,shared.err);
}
}
当AOF rewrite请求被挂起时,在serverCron函数 中,会处理。
Highlighter bg_cpp">
404_151@[cpp]
(server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
server.aof_rewrite_scheduled)
{
rewriteAppendOnlyFileBackground();
}
在serverCron函数中会周期性判断
[cpp]
(server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size)
{
base = server.aof_rewrite_base_size ?
server.aof_rewrite_base_size : 1;
growth = (server.aof_current_size*100/base) - 100;
(growth >= server.aof_rewrite_perc) {
redis Log(REdis _NOTICE,rewriting of AOF on %lld%% growth" ,growth);
rewriteAppendOnlyFileBackground();
}
}
当客户端发送该指令时,config.c中的configSetCommand函数会做出响应,startAppendOnly函数会执行AOF rewrite
[cpp]
(!strcasecmp(c->argv[2]->ptr, )) {
enable = yesnotoi(o->ptr);
(enable == -1) badfmt;
(enable == 0 && server.aof_state != REdis _AOF_OFF) {
stopAppendOnly();
} (enable && server.aof_state == REdis _AOF_OFF) {
(startAppendOnly() == REdis _ERR) {
addReplyError(c,
);
;
}
}
}
Highlighter bg_cpp">
404_151@[cpp]
startAppendOnly( ) {
server.aof_last_fsync = server.unixtime;
server.aof_fd = open(server.aof_filename,0644);
redis Assert(server.aof_state == REdis _AOF_OFF);
(server.aof_fd == -1) {
redis Log(REdis _WARNING,dis needs to enable the AOF but can't open the append only file: %s " ,strerror(errno));
REdis _ERR;
}
(rewriteAppendOnlyFileBackground() == REdis _ERR) {
close(server.aof_fd);
redis Log(REdis _WARNING,dis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error." );
REdis _ERR;
}
server.aof_state = REdis _AOF_WAIT_REWRITE;
REdis _OK;
}
从上述分析可以看出rewrite的实现全部依靠rewriteAppendOnlyFileBackground函数,下面分析该函数,通过下面的代码可以看出,Redis是fork出一个子进程来操作AOF rewrite,然后子进程调用rewriteAppendOnlyFile函数,将数据写到一个临时文件temp-rewriteaof-bg-%d.aof中。如果子进程完成会通过exit(0)函数通知父进程rewrite结束,在serverCron函数中使用wait3函数接收子进程退出状态,然后执行后续的AOF rewrite的收尾工作,后面将会分析。
父进程的工作主要包括清楚server.aof_rewrite_scheduled标志,记录子进程IDserver.aof_child_pid = childpid,记录rewrite的开始时间server.aof_rewrite_time_start = time(NULL)等。
[cpp]
rewriteAppendOnlyFileBackground( ) {
pid_t childpid;
start;
(server.aof_child_pid != -1) REdis _ERR;
start = ustime();
((childpid = fork() ) == 0) {
tmpfile[256];
closeListeningSockets(0);
redis SetProcTitle(dis-aof-rewrite" );
snprintf(tmpfile,256,, ( ) getpid());
(rewriteAppendOnlyFile(tmpfile) == REdis _OK) {
private_dirty = zmalloc_get_private_dirty();
(private_dirty) {
redis Log(REdis _NOTICE,
copy-on-write" ,
private_dirty/(1024*1024));
}
exitFromChild(0);
} {
exitFromChild(1);
}
} {
server.stat_fork_time = ustime()-start;
(childpid == -1) {
redis Log(REdis _WARNING,
%s" ,
strerror(errno));
REdis _ERR;
}
redis Log(REdis _NOTICE,
rewriting started by pid %d" ,childpid);
server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL);
server.aof_child_pid = childpid;
updateDictResizePolicy();
server.aof_selected_db = -1;
replicationScriptCacheFlush();
REdis _OK;
}
REdis _OK;
}
接下来介绍rewriteAppendOnlyFile函数 ,该函数 的主要工作为:遍历所有数据库 中的数据,将其写入到临时文件 temp-rewriteaof-%d.aof中,写入函数 定义在rio.c中,比较简单,然后将数据刷新到硬盘中,然后将文件 名rename为其调用 者给定的临时文件 名,注意仔细看代码 ,这里并没有修改 为正式的AOF文件 名。
在写入文件 时如果设置server.aof_rewrite_incremental_fsync参数,那么在rioWrite函数 中fwrite部分数据就会将数据fsync到硬盘中,来保证数据的正确性。
Highlighter bg_cpp">
404_151@[cpp]
rewriteAppendOnlyFile( *filename) {
dictItera tor *di = NULL;
dictEntry *de;
rio aof;
*fp;
tmpfile[256];
j;
Now = mstime();
snprintf(tmpfile,, ( ) getpid());
fp = fopen(tmpfile,);
(!fp) {
redis Log(REdis _WARNING, opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s " , strerror(errno));
REdis _ERR;
}
rioInitWithFile(&aof,fp);
(server.aof_rewrite_incremental_fsync)
rioSetAutoSync(&aof,REdis _AOF_AUTOSYNC_BYTES);
(j = 0; j < server.dbn um; j++) {
selectcmd[] = ;
redis Db *db = server.db+j;
dict *d = db->dict;
(dictSize(d) == 0) ;
di = dictGetSafeItera tor(d);
(!di) {
fclose(fp);
REdis _ERR;
}
(rioWrite(&aof,selectcmd, (selectcmd)-1) == 0) werr;
(rioWriteBulkLongLong(&aof,j) == 0) werr;
((de = dictNext(di)) != NULL) {
sds keystr;
robj key, *o;
expiretime;
keystr = dictGetKey(de);
o = dictGetVal(de);
initStaticStringObject(key,keystr);
expiretime = getExpire(db,&key);
(expiretime != -1 && expiretime < Now ) ;
(o->type == REdis _STRING) {
cmd[]= ;
(rioWrite(&aof, (cmd)-1) == 0) werr;
(rioWriteBulkObject(&aof,&key) == 0) werr;
(rioWriteBulkObject(&aof,o) == 0) werr;
} (o->type == REdis _LIST) {
(rewriteListObject(&aof,&key,o) == 0) werr;
} (o->type == REdis _SET) {
(rewriteSeto bject(&aof,o) == 0) werr;
} (o->type == REdis _ZSET) {
(rewriteSortedSeto bject(&aof,o) == 0) werr;
} (o->type == REdis _HASH) {
(rewriteHashObject(&aof,o) == 0) werr;
} {
redis Panic(Nown object type" );
}
(expiretime != -1) {
cmd[]= ;
(rioWrite(&aof, (cmd)-1) == 0) werr;
(rioWriteBulkObject(&aof,&key) == 0) werr;
(rioWriteBulkLongLong(&aof,expiretime) == 0) werr;
}
}
dictReleaseItera tor(di);
}
fflush(fp);
aof_fsync(fileno(fp));
fclose(fp);
(rename(tmpfile,filename) == -1) {
redis Log(REdis _WARNING,%s" , strerror(errno));
unlink(tmpfile);
REdis _ERR;
}
redis Log(REdis _NOTICE,);
REdis _OK;
werr:
fclose(fp);
unlink(tmpfile);
redis Log(REdis _WARNING,disk: %s " , strerror(errno));
(di) dictReleaseItera tor(di);
REdis _ERR;
}
AOF rewrite工作到这里已经结束一半,上一篇 文章 提到如果server.aof_state != REdis _AOF_OFF,那么就会将客户端请求指令修改 的数据通过Feed AppendOnlyFile函数 追加到AOF文件 中,那么此时AOF已经rewrite了,必须要处理此时出现的差异数据,记得在Feed AppendOnlyFile函数 中有这么一段代码
Highlighter bg_cpp">
404_151@[cpp]
(server.aof_child_pid != -1)
aofRewriteBufferAppend((unsigned *)buf,sdslen(buf));
如果AOF rewrite正在进行 ,那么就将修改 数据的指令字符串存储到server.aof_rewrite_buf_blocks链表中,等待AOF rewrite子进程结束后处理,处理此部分数据的代码 在serverCron函数 中。需要指出的是wait3函数 我不了解,可能下面注释会有点问题。
Highlighter bg_cpp">
404_151@[cpp]
(server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
statloc;
pid_t pid;
((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
exitcode = WEXITSTATUS(statloc);
bysignal = 0;
(WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
(pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
} (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
} {
redis Log(REdis _WARNING,
,
()pid);
}
updateDictResizePolicy();
}
}
对于AOF rewrite期间出现的差异数据,Server通过backgroundSaveDoneHandler函数 将server.aof_rewrite_buf_blocks链表中数据追加到新的AOF文件 中。
backgroundSaveDoneHandler函数 执行步骤:
1、通过判断子进程的退出 状态,正确的退出 状态为exit(0),即exitcode为0,bysignal我不清楚具体意义,如果退出 状态正确,backgroundSaveDoneHandler函数 才会开始处理
2、通过对rewriteAppendOnlyFileBackground函数 的分析,可以知道rewrite后的AOF临时文件 名为temp-rewriteaof-bg-%d.aof(%d=server.aof_child_pid)中,接着需要打开此临时文件
3、调用 aofRewriteBufferWrite函数 将server.aof_rewrite_buf_blocks中差异数据写到该临时文件 中
4、如果旧的AOF文件 未打开,那么打开旧的AOF文件 ,将文件 描述符赋值给临时变量oldfd
5、将临时的AOF文件 名rename为正常的AOF文件 名
6、如果旧的AOF文件 未打开,那么此时只需要关闭 新的AOF文件 ,此时的server.aof_rewrite_buf_blocks数据应该为空;如果旧的AOF是打开的,那么将server.aof_fd指向newfd,然后根据相应的fsync策略将数据刷新到硬盘上
7、调用 aofUpdateCurrentSize函数 统计 AOF文件 的大小,更新server.aof_rewrite_base_size,为serverCron中自动 AOF rewrite做相应判断
8、如果之前是REdis _AOF_WAIT_REWRITE状态,则设置server.aof_state为REdis _AOF_ON,因为只有“config set appendonly yes”指令才会设置这个状态,也就是需要写完快照后,立即打开AOF;而BGREWRITEAOF不需要打开AOF
下面是backgroundSaveDoneHandler函数 的注释代码
Highlighter bg_cpp">
404_151@[cpp]
backgroundRewriteDoneHandler( exitcode, bysignal) {
(!bysignal && exitcode == 0) {
newfd, oldfd;
tmpfile[256];
Now = ustime();
redis Log(REdis _NOTICE,
);
snprintf(tmpfile,
()server.aof_child_pid);
newfd = open(tmpfile,O_WRONLY|O_APPEND);
(newfd == -1) {
redis Log(REdis _WARNING,
%s" , strerror(errno));
cleanup;
}
(aofRewriteBufferWrite(newfd) == -1) {
redis Log(REdis _WARNING,
%s" , strerror(errno));
close(newfd);
cleanup;
}
redis Log(REdis _NOTICE,
, aofRewriteBufferSize());
(server.aof_fd == -1) {
oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);
} {
oldfd = -1;
}
(rename(tmpfile,server.aof_filename) == -1) {
redis Log(REdis _WARNING,
%s" , strerror(errno));
close(newfd);
(oldfd != -1) close(oldfd);
cleanup;
}
(server.aof_fd == -1) {
close(newfd);
} {
oldfd = server.aof_fd;
server.aof_fd = newfd;
(server.aof_fsync == AOF_FSYNC_ALWAYS)
aof_fsync(newfd);
(server.aof_fsync == AOF_FSYNC_EVERYSEC)
aof_background_fsync(newfd);
server.aof_selected_db = -1;
aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size;
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
server.aof_lastbgrewrite_status = REdis _OK;
redis Log(REdis _NOTICE, );
(server.aof_state == REdis _AOF_WAIT_REWRITE)
server.aof_state = REdis _AOF_ON;
(oldfd != -1) bioCreateBackgroundJob(REdis _BIO_CLOSE_FILE,( *)( )oldfd,NULL);
redis Log(REdis _VERBOSE,
, ustime()-Now );
} (!bysignal && exitcode != 0) {
server.aof_lastbgrewrite_status = REdis _ERR;
redis Log(REdis _WARNING,
);
} {
server.aof_lastbgrewrite_status = REdis _ERR;
redis Log(REdis _WARNING,
, bysignal);
}
cleanup:
aofRewriteBufferReset();
aofRemoveTempFile(server.aof_child_pid);
server.aof_child_pid = -1;
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
server.aof_rewrite_time_start = -1;
(server.aof_state == REdis _AOF_WAIT_REWRITE)
server.aof_rewrite_scheduled = 1;
}
至此,AOF数据持久化已经全部结束了,剩下的就是一些细节的处理,以及一些Linux库函数 的理解,对于rename、unlink、wait3等库函数 的深入认识就去问Google吧。
Redis AOF数据持久化的实现机制通过三篇文章基本上比较详细的分析了, 但这只是从代码层面去看AOF,对于AOF持久化的优缺点网上有很多分析,Redis的官方网站也有英文介绍,Redis的数据持久化还有一种方法叫RDB,更多RDB的内容等下次再分析。
感谢此篇博客给我在理解Redis AOF数据持久化方面的巨大帮助, ,此篇 博客 对AOF的分析十分的详细。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。