🕋【Redis干货领域】从底层彻底吃透AOF重写(原理篇)
🕋 每日一句
不要轻易去依赖一个人,它会成为你的习惯,当分别来临,你失去的不是某个人,而是你精神的支柱。无论何时何地,都要学会独立行走,它会让你走得更坦然些。
🕋 前提介绍
RDB: 将数据库的快照以二进制的方式保存到磁盘; AOF: 将所有写入命令及相关参数以协议文本的方式写入文件并持久保存磁盘;
🕋 AOF的实现
🕋 AOF执行案例
将命令转换成协议文本,转换后的结果:
*3
$3
SET
$3
KEY
$5
VALUE
将协议文本追加到aof缓存,也就是 aof_buf ; 根据sync策略调用 fsync/fdatasync 。 到目前为止已经成功保存数据,如果想要还原AOF,只需要将AOF里命令读出来并重放就可以还原数据库。
🕋 重写的介绍
AOF持久化机制存在一个致命的问题,随着时间推移,AOF文件会膨胀,如果频繁写入AOF文件会膨胀到无限大,当server重启时严重影响数据库还原时间,影响系统可用性。 为解决此问题,系统需要定期重写AOF文件,目前采用的方式是创建一个新的AOF文件,将数据库里的全部数据转换成协议的方式保存到文件中,通过此操作达到减少AOF文件大小的目的,重写后的大小一定是小于等于旧AOF文件的大小 。
🕋 重写的实现
rewrite类似于普通数据库管理系统日志恢复点,当AOF文件随着写命令的运行膨胀时,当文件大小触碰到临界时,rewrite会被运行。 rewrite(bgrewriteaof相似)会像replication一样,fork出一个子进程,创建一个临时文件,遍历数据库内存数据,将每个key、value对输出到临时文件。输出格式就是Redis的命令(RESP),但是为了减小文件大小,会将多个key、value对集合起来用一条命令表达。 rewrite期间的写操作会保存在内存的rewrite buffer中,rewrite成功后这些操作也会复制到临时文件中(指令传播),在最后临时文件会代替AOF文件。以上在AOF打开的情况下,如果AOF是关闭的,那么rewrite操作可以通过bgrewriteaof命令来进行。
🕋 重写的类型
REWRITE: 在主线程中重写AOF,会阻塞工作线程,在生产环境中很少使用,处于废弃状态; BGREWRITE: 在后台(子进程)重写AOF, 不会阻塞工作线程,能正常服务,此方法最常用。
🕋 重写的流程
🕋 实现关键点
由于写操作通常是有缓冲的,所以有可能AOF操作并没有写到硬盘中,一般可以通过fsync()来强制输出到硬盘中。而fsync()的频率可以通过配置文件中的flush策略来指定,可以选择每次事件循环写操作都强制fsync或者每秒fsync至少运行一次。 当rewrite子进程开始后,父进程接受到的命令会添加到aof_rewrite_buf_blocks中,使得rewrite成功后,将这些命令添加到新文件中。在rewrite过程中,原来的AOF也可以选择是不是继续添加,由于存在性能上的问题,在rewrite过程中,如果fsync()继续执行,会导致IO性能受损影响Redis性能。所以一般情况下rewrite期间禁止fsync()到旧AOF文件。这策略可以在配置文件中修改。 在rewrite结束后,在将新rewrite文件重命名为配置中指定的文件时,如果旧AOF存在,那么会unlink掉旧文件。这是就存在一个问题,处理rewrite文件迁移的是主线程,rename(oldpath, newpath)过程会覆盖旧文件,这是rename会unlink(oldfd),而unlink操作会导致block主线程。这时,我们就需要类似libeio(http://software.schmorp.de/pkg/libeio.html)这样的库去进行异步的底层IO。作者在bio.c有一个类似的机制,通过创建新线程来进行异步操作。
🕋 异步重写的支持
🕋 Rewrite存在的问题
🕋 自动触发条件
AOF里存放了所有的redis 操作指令,文件达到条件或者手动bgrewriteaof命令都可以触发rewrite。
long long growth =(server.appendonly_current_size*100/base) - 100;
if (growth >=server.auto_aofrewrite_perc)
在配置文件里设置过: