Redis的AOF日志

331 阅读6分钟

如何避免宕机后数据丢失?

一个事实是一旦服务器宕机,内存中的数据将全部丢失.这样就需要一种机制进行数据恢复,日志就是一种很好的方式.


日志分类

  1. 写前日志:在实际写数据前先把修改的日志数据记录到文件中,以便故障时进行回复.
  2. 写后日志(redis采用这种方式称之为AOF日志):redis先执行命令,把数据写入内存,然后才记录日志,称之为AOF日志.

AOF记录了什么内容?

传统数据库日志记录的是修改后的数据,而AOF日志记录的是redis收到的每一条命令,这些命令以文本的形式保存.

示例过程如下:
例如set testkey testvalue这个命令,redis会以*3 $3 set $7 testkey $9 testvalue的形式保存在AOF文本文件中.
其中,*3表示命令有三个部分,$3表示这个部分的字节数 set等就是这部分本身的文本.


为什么使用写后日志?

首先,为了避免额外的检查开销.
redis在向AOF记录日志的时候不会对这些命令进行语法上的检查,所以先记录日志再执行写命令的话,日志中可能记录的错误的命令,这在使用AOF进行数据恢复的时候就会出现错误.

其次,为了避免记录错误的命令
redis让系统先执行命令,只有命令执行成功才会被记录到日志中,否则系统会执行向客户端报错.

命令执行之后才记录日志,因此不会阻塞当前的写操作


潜在的风险

首先,数据丢失的风险
如果刚执行完一个命令还没来得及记录日志就宕机,这个命令和相应的数据就会有丢失的风险.

其次,对下一个写操作带来阻塞的风险
AOF日志的这种写后日志的方式,极大的避免了对当前操作的阻塞,但是AOF日志的记录也是在主线程中执行的,如果在记录日志时写盘缓慢就会导致后续的操作无法执行.

如果避免风险?

仔细分析,这两个风险都是和AOF机制写盘时机有关,如果我们能够控制一个写命令执行完后AOF日志写回磁盘的时机,这个风险解除了.

AOF机制提供了三种选择,也就是AOF配置项appendfsync的三个可选项.

三种写回策略
  • Always,同步写回,可靠性高,性能影响较大:每个写命令执行完,乐可同步的将日志写回磁盘中.
  • Everysec,每秒写回,性能始终,有丢失一秒数据的风险:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘.
  • No,操作系统控制的写回,性能好,宕机时丢失数据较多:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘.

三种写回策略都无法做到两全其美.
并且会产生这样一种情况就是AOF是以文件的形式记录接受到的所有的写命令,随着接收到的命令越来越多,AOF日志文件就会越来越大,这也意味着,我们一定小心AOF日志文件过大带来的性能问题.

这里的性能问题主要体现在三个方面:

  1. 文件系统本身对文件大小有限制,无法保存过大的文件.
  2. 如果文件过大之后再往文件中追加命令记录的话效率也会相应变低.
  3. 如果发生宕机,AOF中记录的命令要一个个被重新执行用于故障恢复,如果日志文件过大整个恢复过程就会变得非常缓慢从而影响redis的正常使用.

这样就带来了另一个问题,文件过大怎么办?


日志文件过大怎么办?AOF重写机制

AOF重写机制就是在重写时,redis根据数据库的现状创建一个新的AOF文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入.

为什么重写机制可以把日志文件变小呢?

可以看到上面定义中加粗的文字,重写机制有"多变一"的功能,也就是说,就日志文件中的多条命令,在重写后会以一条命令的形式保存在AOF重写后的日志中. 解释一下,因为AOF文件是以追加的方式逐一记录接受到的写命令的,当一个键值对被多条写命令反复修改时,AOF文件会记录相应的多条命令,但是重写机制会在重写的时候根据这个键值对的最新状态为它生成对应的写入命令,这样一个键值对在重写日志中只用一条写命令就记录了原来日志中的所有的键值对写入历程,同样的在进行数据恢复的时候只需要执行这一个命令就恢复到了原来需要多次操作才能到达的状态.这对一个键值对执行了多次写命令的情况节省下的日志空间是非常可观的.

但是这也带来了一个问题,AOF重写机制会阻塞线程从而影响redis的性能吗?

AOF重写会阻塞吗?

答案当然是不会的.

AOF日志机制的写回操作是由redis的主线程负责的,而重写过程是由后台的一个子进程bgrewriteaof负责的,这样就避免了阻塞主线程从而影响redis的性能.

重写过程

可以概括为"一个拷贝,两处日志".

一个拷贝是指每次执行重写操作的时候,主线程会fork出后台的子进程bgrewriteaof子进程.主线程会复制一份内存给子进程,这个内存中包含了redis中最新的数据,这样在不影响主线程的情况下,子进程就可以执行重写操作将内存中的数据重新到另一份AOF日志文件中.

两处日志是指主线程现在正在使用的AOF日志文件和新的AOF重写日志.
由于重写机制并没有阻塞主线程,仍然可以处理新的操作.
如果有写操作,redis仍然会使用主线程正在使用的AOF日志文件进行记录并把这个操作写入缓冲区同时也被写到重写日志的缓冲区.
这样即使真的宕机了,第一个AOF日志文件也是完整的可以用来恢复数据.
可以看到如果真的执行了新的写命令操作,重写日志的内存缓冲区中也存在新的操作,说明重写日志也不会丢失新的写命令操作. 等到拷贝数据操作的所有记录和重写日志的内存缓冲区中的最新操作读写入新的AOF日志文件中,就可以使用新的AOF日志文件代替主线程现在正在使用的AOF日志文件,也就是主线程现在可以正式的直接操作重写操作之后的AOF日志了.

具体过程如下图所示: AOF重写机制示意图