zoukankan      html  css  js  c++  java
  • AOF日志

      提到日志,比较熟悉的数据库的写前日志(Write Ahead Log,WAL),也就是说,在实际写入数据之前,先把修改的数据记录在日志文件中,以便故障时进行恢复。不过AOF日志正好相反,它是写后日志,“写后”的意思是Redis是先执行命令,把数据写入内存,然后踩记录日志,如下所示:

                Redis AOF操作过程

         那么为什么AOF要先执行命令再记日志呢?先来看一下AOF日志里记录了什么内容。

      传统数据库的日志,例如redo log(重做日志)记录的是修改后的数据,把什么修改成了什么。而AOF里记录的是Redis收到的每一条命令,这些命令都是以文本形式保存的。

      我们以Redis收到"set testkey testvalue"命令后记录的日志为例,看看AOF日志的内容。其中,"*3"表示当前命令有三个部分,每部分都是由"$+数字"开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分的命令、键或值一共由多少字节。例如,"$3 set"表示这部分有3个字节,也就是"set"命令。

               Redis AOF文件内容

      为了避免额外开销,Redis在向AOF里面记录日志的时候,并不会去对这些命令进行语法检查。所以,如果先记日志在执行命令的话,日志中可能记录了错误的命令,Redis在使用日志恢复数据的时候,就可能会出错。

      而写后日志这种方式,就是让系统内执行命令,只有命令执行成功,才会被记录在日志中,否则,系统会直接向客户端报错。所以Redis使用写后日志这一方式的一大好处就是,可以避免出现记录错误命令的情况。

      除此之外,AOF还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作。

     不过,AOF也有两个潜在的风险:

      首先,如果刚执行完一个命令,还没有来得及记录日志就宕机了,那么这个命令和相应的数据就有丢失的风险。如果此时Redis是用作缓存的,还可以从后端数据库重新读入数据进行恢复,但是,如果Redis是直接用作数据库的话,此时因为命令没有记入日志,所以就无法用日志进行恢复了。

      其次,AOF虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。

      仔细回想,就会发现这两个风险都和AOF写回磁盘的时机有关,也就意味着,如果能够控制一个写命令执行完后AOF日志写回磁盘的时机,这两个风险就解除了。

    三种回写策略

      对于以上问题,Redis给我们提供了3个选择,也就是Redis配置中的appendfsync的三个可选项

    • Always:同步回写,每个写命令执行完成后,立马同步地将日志写回磁盘
    • everysec:每秒回写,每个写命令执行完成后,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘。
    • No:操作系统控制的写回,每个命令执行完成,只是先把日志写在AOF文件的内存缓冲区,有操作系统决定何时将缓冲区的内容写回磁盘。

       三种策略的写回时机:

     

      由于AOF是以文件的形式记录执行的所有命令,随着接收的命令越来越多,AOF会越来越大,也就意味着AOF文件过大带来的性能问题。这里的性能问题主要包括三个方面:

    • 文件系统本身对文件大小有限制,无法保存太大的文件
    • 如果发生宕机,AOF的记录命令要一个个被执行,如果日志文件太大,整个恢复过程就会非常缓慢,影响Redis的正常使用
    • 文件本身过大,再往里追加命令记录,效率会变低

      所以,Redis给我们提供了AOF重写机制

     AOF重写机制

      AOF重写机制就是在重写时,重新生成一个AOF文件,也就是说,将数据库中现在所有的键值对,然后对每一个键值对用一条命令记录它的写入。为什么重写机制能够使日志文件变小呢?实则在执行重写时会把多条命令合并整理为一条命令,如lpush list a,lpush list b,lpush list c可以直接转化为lpush list a b c。

    AOF重写操作是否会阻塞?

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

      1、在每次执行AOF重写时,主线程都会fork出一个bgrewriteaof子进程。注意fork的瞬间是会阻碍主线程的,同时fork子进程时,子进程是拷贝父进程的页表,即虚实映射关系,而不会拷贝物理内存。子进程复制了父进程的页表,也能共享访问父进程的内存数据了,此时就类似有了父进程中所有的内存数据。然后bgrewriteaof子进程就可以在不影响主线程的情况下,逐步把拷贝的数据写成操作,记录在重写日志中。

      注:fork采用操作系统提供的写时复制(Copy-on-Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题。在写数据的时候,才真正拷贝内存中的数据,这个过程中,父进程是可能存在阻塞的风险。

      2、因为主线程未阻塞,依旧可以处理新来的操作。如果由写操作,Redis会把这个操作写到正在使用的的那个AOF文件的缓冲区。同时这个时候如果新的写入操作也会被写到新的AOF重写日志中,这样重写日志也不会丢失最新的写操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的AOF文件,保证数据库的最新状态记录。此时就可以使用新的AOF文件代替旧的AOF文件了。

                

    总结:每次AOF重写,都会执行一个内存拷贝,将内存中的数据拷贝用于重写;然后,两个日志文件在重写的过程中,新写入的数据不丢失,而且,因为Redis使用另外的线程来进行数据重写,所以这个过程不会阻塞主线程。

  • 相关阅读:
    [leetcode] Combinations
    Binary Tree Level Order Traversal I II
    [leetcode] Remove Duplicates from Sorted Array I II
    [leetcode] Permutations II
    [leetcode] Permutations
    如何在线程间进行事件通知?
    如何实现迭代对象和迭代器对象?
    如何判断字符串a是否以字符串 b开头或者结尾?
    如何实现用户的历史记录功能(最多n条)?
    如何让字典保持有序?
  • 原文地址:https://www.cnblogs.com/wushaoyu/p/15103612.html
Copyright © 2011-2022 走看看