Redis深度历险-AOF持久化
Redis提供两种持久化方式AOF和RDB,RDB是快照形式持久化全量数据、AOF是增量持久化记录执行命令
AOF原理
struct redisServer {
........
sds aof_buf; //AOF缓冲区
AOF持久化的是Redis执行的写入命令,Redis会将执行的写入命令放入AOF缓冲区中;而在Redis的定时任务server.c/serverCron
中将数据写入到磁盘中
在恢复数据时按照顺序依次执行即可恢复数据。
AOF配置
-
appendonly yes
:是否开启AOF持久化 -
appendfilename appendonly.aof
:文件名 -
appendfsync
:磁盘写入策略,主要是因为文件IO有缓冲区可能数据没有真正写入到文件-
always
:每次写入磁盘,性能较差 -
everysec
:每秒写入一次 -
no
:系统处理缓存回写
-
在这里面影响最大的是磁盘写入策略,因为调用write
写入的是系统缓冲区而没有真正落盘,如果系统发生宕机则会造成数据丢失,不过Linux提供fsync
和fdatasync
等接口将数据刷入硬盘中
磁盘写入策略就是控制落盘的时机,需要根据系统需求在数据和性能中作出取舍
当然,调用write
接口也是注册事件到Redis的事件循环中一并处理的
AOF实现
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
........
//将命令写入到aof缓冲区中
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
//如果正在进程AOF重写时,则同样将输入发送给AOF重写子进程
if (server.child_type == CHILD_TYPE_AOF)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
AOF重写
AOF的问题在于存储的是明文命令且存在冗余,文件内容会非常大,在恢复数据时耗时非常长
AOF重写原理
SET "A" "B"
DEL "A"
SET "A" "C"
SET "A" "D"
类似于这样执行后,Redis中实际上只有A
这样一个键,AOF中存储的四条命令实际上存在冗余;AOF重写的原理就是根据内存中的数据将AOF文件重写为:
SET "A" "D"
在恢复数据时最终得出的结果时一致的
AOF重写实现
执行
bgrewriteaof
命令即可进行AOF重写
int rewriteAppendOnlyFileBackground(void) {
pid_t childpid;
if (hasActiveChildProcess()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR;
//创建一个子进程来执行重写指令
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
char tmpfile[256];
//并不是在AOF文件中直接重写,而是重写到一个新的文件最终原子行替换
redisSetProcTitle("redis-aof-rewrite");
redisSetCpuAffinity(server.aof_rewrite_cpulist);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
exitFromChild(0);
} else {
exitFromChild(1);
}
} else {
/* Parent */
if (childpid == -1) {
serverLog(LL_WARNING,
"Can't rewrite append only file in background: fork: %s",
strerror(errno));
aofClosePipes();
return C_ERR;
}
serverLog(LL_NOTICE,
"Background append only file rewriting started by pid %ld",(long) childpid);
server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL);
server.aof_selected_db = -1;
replicationScriptCacheFlush();
return C_OK;
}
return C_OK; /* unreached */
}
- AOF重写是不会阻塞Redis执行的,因为创建了子进程来执行
- AOF重写也是不会影响AOF文件的,是在一个临时文件中写入数据的,最终用
rename
来替换
int rewriteAppendOnlyFileRio(rio *aof) {
dictIterator *di = NULL;
dictEntry *de;
size_t processed = 0;
int j;
long key_count = 0;
long long updated_time = 0;
//逐个遍历所有db
for (j = 0; j < server.dbnum; j++) {
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetSafeIterator(d);
//在db切换时记录select命令
if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
//直接遍历内存中的字典
while((de = dictNext(di)) != NULL) {
sds keystr;
robj key, *o;
long long expiretime;
keystr = dictGetKey(de);
o = dictGetVal(de);
initStaticStringObject(key,keystr);
expiretime = getExpire(db,&key);
//存储命令、key、value
if (o->type == OBJ_STRING) {
/* Emit a SET command */
char cmd[]="*3\r\n$3\r\nSET\r\n";
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
/* Key and value */
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
if (rioWriteBulkObject(aof,o) == 0) goto werr;
........
AOF重写直接就是遍历所有数据库的所有键值,存储到AOF文件中
AOF重写缓冲区
AOF重写过程中主进程接收到新的指令就会放入到AOF重写缓冲区中
子进程
int rewriteAppendOnlyFile(char *filename) {
........
while(mstime()-start < 1000 && nodata < 20) {
if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
{
nodata++;
continue;
}
nodata = 0; /* Start counting from zero, we stop on N *contiguous*
timeouts. */
aofReadDiffFromParent();
}
同时AOF重写子进程会接收到重写过程中Redis进程执行的指令并同样记录到文件中
父进程
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
listNode *ln = listLast(server.aof_rewrite_buf_blocks);
........
重写缓冲区是以链表实现的,一段一段的通过事件循环发送给子进程;最终如果子进程结束后还有剩余,由主进程将数据追加写入到重写后的AOF文件中
网友评论