数据库内核月报 - 2016 / 03

Redis · 特性分析 · AOF Rewrite 分析

AOF介绍

Redis提供两种持久化机制

  1. RDB: 将数据库的快照以二进制的方式保存到磁盘;
  2. AOF: 将所有写入命令及相关参数以协议文本的方式写入文件并持久保存磁盘。

本文只关心AOF,简单介绍一下:Redis Server将所有写入的命令转换成协议文本的方式写入AOF文件,例如:Server收到 set key value的的写入命令,server会进行以下几步操作:

  1. 将命令转换成协议文本,转换后的结果:*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
  2. 将协议文本追加到aof缓存,也就是aof_buf;
  3. 根据sync策略调用fsync/fdatasync。

到目前为止已经成功保存数据,如果想要还原AOF,只需要将AOF里命令读出来并重放就可以还原数据库。

AOF持久化机制存在一个致命的问题,随着时间推移,AOF文件会膨胀,如果频繁写入AOF文件会膨胀到无限大,当server重启时严重影响数据库还原时间,影响系统可用性。为解决此问题,系统需要定期重写AOF文件,目前采用的方式是创建一个新的AOF文件,将数据库里的全部数据转换成协议的方式保存到文件中,通过此操作达到减少AOF文件大小的目的,重写后的大小一定是小于等于旧AOF文件的大小。

重写AOF提供两种方式

  1. REWRITE: 在主线程中重写AOF,会阻塞工作线程,在生产环境中很少使用,处于废弃状态;
  2. BGREWRITE: 在后台(子进程)重写AOF, 不会阻塞工作线程,能正常服务,此方法最常用。

本文只关心BGREWRITE的问题,因此只介绍此命令的实现机制。

AOF后台Rewrite实现方式

Server收到BGREWRITE命令或者系统触发AOF重写时,主进创建一个子进程并进行AOF重写,主进程异步等待子进程结束(信号量),此时主进程能正常接收处理用户请求,用户请求会修改数据库里数据,会使得当前数据库的数据跟重写后AOF里不一致,需要有种机制保证数据的一致性。当前的做法是在重写 AOF 期间系统会新开一块内存用于缓存重写期间收到的命令,在重写完成以后再将缓存中的数据追加到新的AOF。在处理命令时既要将命令追加到 aof_buf,也要追加到重写AOF Buffer。

AOF后台Rewrite存在的问题

重写AOF Buffer是个不限大小的buffer,但用户写入的数据量较多时会出现以下两个问题:

  1. 占用过多内存,浪费资源;
  2. 主进程将AOF buffer数据写入到新AOF文件中时会阻塞工作线程,用户正常请求的延时会变高,严重情况下会超时,主备同步也会出问题,断开重连,重新同步等。

AOF后台Rewrite解决方案

官方解决方案

主要思路是AOF重写期间,主进程跟子进程通过管道通信,主进程实时将新写入的数据发送给子进程,子进程从管道读出数据交缓存在buffer中,子进程等待存量数据全部写入AOF文件后,将缓存数据追加到AOF文件中,此方案只是解决阻塞工作线程问题,但占用内存过多问题并没有解决。

新解决方案

主要思路是AOF重写期间,主进程创建一个新的aof_buf,新的AOF文件用于接收新写入的命令,sync策略保持不变,在AOF重写期间,系统需要向两个aof_buf,两个AOF文件同时追加新写入的命令。当主进程收到子进程重写AOF文件完成后,停止向老的aof_buf,AOF文件追加命令,然后删除旧的AOF文件(流程跟原来保持一致);将将子进程新生成的AOF文件重命名为appendonly.aof.last,具体流程如下:

  1. 停止向旧的aof_buf,AOF文件追加命令;
  2. 删除旧的的appendonly.aof.last文件;
  3. 交换两个aof_buf,AOF文件指针;
  4. 回收旧的aof_buf,AOF文件;
  5. 重命令子进程生成的AOF文件为appendonly.aof.last;

系统运行期间同时存在两个AOF文件,一个是当前正在写的AOF,另一个是存量的AOF数据文件。因此需要修改数据库恢复相关逻辑,加载AOF时先要加载存量数据appendonly.aof.last,再加载appendonly.aof。