AOF日志:宕机了,Redis如何避免数据丢失?

138 阅读6分钟

AOF 日志如何实现的?

说到日志,我们比较熟悉的数据块的写前日志,也就是说,在实际写数据前,先把修改的数据记录到日志文件中。AOF(append only file),日志正好相反,它是写后日志,“写后”的意思redis先执行命令,把数据写入内存,然后才记录日志。

image.png

那么AOF为什么要先执行命令在写日志呢?

传统数据库的日志,例如redo.log(重做日志),记录的是修改后的数据,而AOF里记录的是REDIS收到的每益添命令,这些命令是以文本形式保存的。

我们已redis收到set testkey testValue命令后记录的日志为例,看看AOF日志的内容。其中,"*3"表示当前命令有三部分组成,每个部分都有“$+数字”开头,后面紧跟着具体的命令、健和值。这里,“数字”表示这部分中的命令、键和值一种多少字节。

image.png

AOF三种写回策略

1:ALWAYS,同步写回:每个命令执行完,立马同步将日志写回磁盘。

2:EVERYSEC,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓存区,每隔一秒吧缓冲区中的内容写入磁盘。

3:No,操作系统控制的写回:每个写命令执行完,知识先把日志写到AOF文件的内存缓冲区,由系统决定何时将缓冲区内容写回磁盘

image.png

日志文件太大怎么办?

重写的时候,是根据这个键值对当前的最新状态,为它生成对应的写命令。这样一来,一个键值对在重写日志中只用一条写命令就行。而且,在日志恢复时,只用执行这条命令,就可以直接完成这个键值对的写入了。

image.png

AOF重写会阻塞吗?

和AOF日志由主线程写回不同,重写过程是由后台子进程bgrewriteaof来完成,这也是为了避免阻塞主线程,导致数据库性能下降。

一个拷贝,两处日志

一个拷贝

一个拷贝是指,每次执行重写时,主线程fork出后台的bgwriteaof子进程。此时,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteof子进程就可以不影响主线程的情况下,逐一把拷贝的数据携程操作,记入重写日志。

两处日志

因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有些操作,第一处日志就是指正在使用的AOF日志,redis会把这个操作写到他的缓冲区。这样一来,即使宕机了,这个AOF日志的操作仍然是齐全的,可以用于恢复。

第二处日志,就是指新的AOF重写日志。这个操作也会被写到重写日志的缓冲区,这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些操作也会重新的AOF文件,以保证数据库最新的状态的记录。此时我们就可以用心的AOF文件代替旧的文件了。

image.png

两个小问题

1:AOF日志重写的时候,是由bgrewriteof子进程来完成的,不用主线程参与,我们今天说的非阻塞的也是指子进程的执行不阻塞主线程。但是你觉得,这个重写过程有没有其他潜在的阻塞风险?如果有的话,会阻塞哪里?

答:fork子进程和AOF重写过程中父进程产生写入的场景。

a:fork子进程,fork的一瞬间一定会阻塞主线程的,fork时并不会一次性拷贝所有内存数据给子进程。fork采用操作系统提供的写实复制机制,就是为了避免一次性拷贝大量内存数据给子进程造成长时间阻塞问题,但fork子进程需要拷贝进程的必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞的时间取决于示例内存的大小。实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址,也就是说此时产了子进程,但是并没有申请与父进程相同的内存大小。那么父进程才会真正内存分离呢?“写实分离”顾名思义,就是在写发生时,才真正拷贝内存的真正数据,这个过程中,父进程也可能会产生阻塞的分享。

b:fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入AOF文件中。但是此时父进程依旧还有流量写入的,如果父进程操作的一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应内存数据,申请新的内存空间,这样逐渐地,父进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因此内存分配是以页为单位进行分配的,默认4K,如果父进程此时操作是bigkey,重新申请大块内存耗时过长,会产生阻塞风险。另外,如果操作系统开启了内存大页机制(HUGE PAGE,页面大小2M)那么父进程申请内存时阻塞的概率将会大大提高,所以redis机器需要关闭huge page机制。redis每次fork生成RDB或aof完成后,都可以在redis log中看到父进程重新申请的内存空降。

2:AOF重写也有一个重写日志,为什么它不共享使用AOF本身的日志呢? 答:AOF重写不复用AOF本身的日志,一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味这个会影响父进程的性能。二是如果AOF重写过程中失败了,那么原来的AOF文件相当于被污染了,无法做恢复的使用。所以redis AOF重写一个新文件,重写失败的话,直接删除这个就好,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件。