AOF持久化设计
RDB持久化会直接保存某一时刻的数据快照,而AOF持久化是直接记录Redis执行过的命令来记录数据库状态。
AOF持久化的实现
AOF持久化功能可以分为命令追加,文件写入,文件同步这三个步骤。
命令追加
如果AOF功能处于开启状态,当Redis执行了一个写命令后,会将执行的写命令追加到aof_buf缓冲区末尾。
1 | // 这个redisServer保存了书中从前到后所展现过的所有结构 |
文件写入
Redis服务器进程是一个事件循环,这个循环中文件事件负责接受客户端请求,以及发送回复命令,还有时间事件等,每个事件负责处理一部分内容。
在处理文件事件时可能会执行一些写命令,就会有一部分内容被追加到aof_buf缓冲区中,所以循环中每次结束事件,都会调用flushAppendOnlyFile函数,考虑是否将缓冲区中的内容追加到AOF文件当中。伪代码如下:
1 | void loop() { |
flushAppendOnlyFile怎么执行由appendfsync来配置。具体如下:
always:将缓冲区所有内容写入并同步到AOF文件。
everysec:将缓冲区所有内容写入到AOF文件,如果上次上次同步时间距离这次超过1秒,那么再次对AOF文件进行同步。这个同步操作是由一个线程专门操作的。
no:将内容写入AOF,但不进行同步,什么时候同步由操作系统决定。
文件的写入与同步
现代的操作系统为了提升效率,在文件写入时会先把数据写入缓冲区,在缓冲区写满或者超出指定时间再去写入文件。这里的写入不进行同步指的是写入到操作系统设计的写入缓冲区,但是还没有真正的写入AOF文件,等到缓冲区写满就会被真正的写入AOF文件。
这三种策略的效率与安全性
always:因为每次都会写入并且同步,安全性最高,即使宕机也只会丢失一个时间循环中所修改的数据。但是同步过程牵扯到写磁盘操作,所以效率比较低。
everysec:每隔一秒会同步一次,效率可以接受,安全性只有可能丢失1s的数据。
no:效率最高,因为不进行同步,如果宕机则会丢上次同步到当前所修改的所有数据。
AOF重写
AOF重写用于缩小AOF文件,考虑一种场景,redis存了一个key为msg,值为hello world的键值对,然后对其做了数次修改,最终又回到了hello world,但是AOF文件却记录了很多条命令,这些命令其实是多余的。AOF重写就是为了去掉这些多余的命令。
重写的实现
AOF重写是用一个新的AOF文件来代替旧的AOF文件。生成新的AOF文件并不需要对旧的AOF文件进行读取,而是根据当前数据库数据生成的。比如list集合中有2个元素,然后经过多次操作最终有5个元素。其实可以直接根据这5条记录生成命令,然后写入新的AOF文件。
伪代码如下:
1 | void aof_rewrite(new_file_name) { |
AOF后台重写
因为AOF重写期间会执行大量的写入文件操作,所以如果用主进程去进行重写,则会造成阻塞。所以Redis将AOF重写放入子进程中。这样有两个好处,一是父进程可以继续处理请求,二是子进程带有服务器进程数据副本,使用子进程而不是子线程,可以在避免使用锁的情况下保证数据安全。
有一点需要注意,在执行AOF重写时,主进程还会处理请求,意味着已经写入的数据可能已经被修改。为了处理这个问题,Redis设置了一个AOF重写缓冲区,在创建子进程开始后,主进程处理的命令会同时写入AOF缓冲区和AOF重写缓冲区。
在子进程重写完成后,会告知主进程,然后主进程会将AOF重写缓冲区的内容写入AOF缓冲区,这样就保证了数据一致。