Redis AOF 持久化 实现原理
文章目录
1. AOF 概念
From now on, every time Redis receives a command that changes the dataset (e.g. SET)
it will append it to the AOF.
When you restart Redis it will re-play the AOF to rebuild the state.
写入的命令以追加到文件的形式保存到文件中 重启的时候对命令进行重现
AOF 是解决 RDB 会丢失一部分数据则出来的持久久,每写一条命令,就往文件中追加一次
但这样有可能会就会造成一个文件非常的大,并且在重启的时候,对命令重现的时候,非常的久
所以 AOF 也有类似的镜像模式
2. AOF 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
appendonly no #是否开启AOF,默认no appendfilename "appendonly.aof" #aof文件 appendfsync #always 每条命令都刷盘 #everysec 每秒刷盘 #no 内核决定什么时候刷 no-appendfsync-on-rewrite no #是否开启rdb类似方式rewrite镜像写 auto-aof-rewrite-percentage 100 #AOF文件是上一次重写的时候的1倍大小 auto-aof-rewrite-min-size 64mb #并于AOF文件要大于64M aof-load-truncated yes #当数据恢复的时候,假如遇到数据损坏,是继续还是直接退出 aof-rewrite-incremental-fsync yes #当 aof rewrite 的时候开启32MB数据,刷一次盘 |
3. AOF 在实现过程中重要成员
3.1 AOF 状态
1 2 3 4 5 6 7 |
/* AOF states */ #define REDIS_AOF_OFF 0 /* AOF 关闭 */ #define REDIS_AOF_ON 1 /* AOF 开启 */ #define REDIS_AOF_WAIT_REWRITE 2 /* 在线修改 为 appendonly yes 的时候 正在rewrite */ |
3.2 重要变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 要刷到AOF文件的数据,string类型 server.aof_buf # rewrite 期间,执行的命令缓存 ,是一个链表 server.aof_rewrite_buf_blocks #距上次rewrite之后 AOF 刷了多少数据,每次rewrite之后清空 server.aof_current_size #上一次rewrite的时候,AOF数据大小 server.aof_rewrite_base_size |
4. AOF rewrite
假如 一直往一个文件中追加命令,那想想,那一个文件不是有可能无限大了?
再想想,假如一个key写一1000万次,那这key就得重现1000万次,那感觉是不是不爽?
所以作者也实现了 AOF rewrite,在一定情况下,将内存中的数据镜像的形式刷到文件,然后在那个时间点之后再进行 命令追加的形式保存
4.1 AOF rewrite 开启一个子进程
和 RDB 开启一个子进程一样,以 copy on write 的技术开启一个子进程
4.2 AOF rewrite 刷盘过程
4.2.1 创建一个 temp-rewriteaof-%d.aof 文件,并打开
4.2.2 将 aof rewrite 镜像数据以二进制形式追加到文件 temp-rewriteaof-%d.aof(这个过程是到内核)
4.2.3 每 32MB 数据将数据从内核fsync方式刷到磁盘
4.2.4 关闭句柄
4.2.5 将 temp-rewriteaof-%d.aof 改名为 temp-rewriteaof-bg-$pid.aof
4.3 AOF rewrite 子进程结束之后回调
在 aof rewrite 子进程结束之后,由主进程回调函数,根据 appendfsync 配置进行一次刷盘调用
1). 在rewrite期间新增的数据 追加到 temp-rewriteaof-bg-$pid.aof
2). 将AOF文件名修改为 appendonly.aof
3). aways 刷盘模式而主线程立马调aof_fsync 刷盘
4). everysec 刷盘模式而提交给另外一个线程异步刷盘
5). 提交给关闭句柄的线程,关闭 旧的AOF文件句柄
4.4 AOF rewrite 触发
4.4.1 定时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* Trigger an AOF rewrite if needed */ if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && server.aof_rewrite_perc && 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) { redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth); rewriteAppendOnlyFileBackground(); } } |
1). 默认100ms 检测一次
2). 没有rdb,aof rewrite 正在执行
3). 当前AOF数据大小 大于 设置的最小rewrite大小 ,默认64MB(auto-aof-rewrite-min-size参数)
4). (server.aof_current_size*100/base) - 100 >= server.aof_rewrite_perc 当前AOF数据大小 是否是上一次的多少倍,默认为 1 倍 (auto-aof-rewrite-percentage 参数)
4.4.2 手工触发 - BGREWRITEAOF 命令
1 2 3 4 5 6 7 8 9 10 11 12 |
void bgrewriteaofCommand(redisClient *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 if (rewriteAppendOnlyFileBackground() == REDIS_OK) { addReplyStatus(c,"Background append only file rewriting started"); } else { addReply(c,shared.err); } } |
立马开启一个子进程进行rewrite
但命令结果返回给客户端之后不一定rewrite完了
4.4.3 手工触发 - CONFIG appendonly yes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
} else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) { int enable = yesnotoi(o->ptr); if (enable == -1) goto badfmt; if (enable == 0 && server.aof_state != REDIS_AOF_OFF) { stopAppendOnly(); } else if (enable && server.aof_state == REDIS_AOF_OFF) { if (startAppendOnly() == REDIS_ERR) { addReplyError(c, "Unable to turn on AOF. Check server logs."); return; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* Called when the user switches from "appendonly no" to "appendonly yes" * at runtime using the CONFIG command. */ int startAppendOnly(void) { server.aof_last_fsync = server.unixtime; server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644); redisAssert(server.aof_state == REDIS_AOF_OFF); if (server.aof_fd == -1) { redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't open the append only file: %s",strerror(errno)); return REDIS_ERR; } if (rewriteAppendOnlyFileBackground() == REDIS_ERR) { close(server.aof_fd); redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error."); return REDIS_ERR; } /* We correctly switched on AOF, now wait for the rerwite to be complete * in order to append data on disk. */ server.aof_state = REDIS_AOF_WAIT_REWRITE; return REDIS_OK; } |
当原本是关闭的情况下
立马开一个子进程rewrite
假如rewrite失败了,每次定时任务都会尝试重试rewrite
5. Redis 写命令
每条写命令在更新了内存之后,都会有一个buf 来保存每命令的数据,不管 appendfsync 配置的是什么参数,这个buf 都是 Redis 自已的,并不是刷到内核里
1) . 只要开启了 AOF,就写到 server.aof_buf
2). 假如开启了rewrite进程 期间,将数据额外追加一份到 server.aof_rewrite_buf_blocks。这份数据是用于在 AOF rewrite 进程退出之后,立马将这份数据刷到 aof 文件末尾的
6. appendfsync everysec
1 2 3 4 5 6 7 8 9 10 11 12 |
/* AOF postponed flush: Try at every cron cycle if the slow fsync * completed. */ if (server.aof_flush_postponed_start) flushAppendOnlyFile(0); /* AOF write errors: in this case we have a buffer to flush as well and * clear the AOF error in case of success to make the DB writable again, * however to try every second is enough in case of 'hz' is set to * an higher frequency. */ run_with_period(1000) { if (server.aof_last_write_status == REDIS_ERR) flushAppendOnlyFile(0); } |
1). 每秒触发一次调用刷盘函数
2). 每次调用函数刷盘,会判断上一次刷盘是否完成,
如果没有完成,则100ms后的定时任务继续调用
3). 将数据从buf刷到内核
4). 提交给另外一个线程 异步调用aof_fsync 将数据从内核刷到磁盘
7. appendfsync aways
每条命令都刷盘一次
1). 每个事件处理之前 将server.aof_buf 里的数据刷到内核。记住并不是每条命令之后立马将数据落盘,而是等待下一个事件执行之前落盘
2). 主线程调用 aof_fsync 从内核刷到磁盘。这里是由主线程执行调用 fsync 刷到磁盘
8. appendfsync no
1). 每个事件处理之前 将server.aof_buf 里的数据刷到内核
2). 将 buf 刷到内核之后,将由内核决定什么时候刷到磁盘,redis 进程不再接管flush这个事情
9. 小结
1). AOF 也会有rewrite刷盘,也是开子进程,cony on write
2). AOF 默认情况下是 当前AOF数据大小是上一次rewrite的倍的时候触发
3). AOF everysec 刷盘,是每秒从buf刷到内核后fsync刷到磁盘, 并不是每条命令就往内核里刷一次数据
4). AOF aways 刷盘,是写命令后的下一个事件执行之前由主线程执行刷盘的,并非写命令之后立马刷盘
文章作者 jc3wish
上次更新 2019-12-02