美文网首页
redis(二:日志)

redis(二:日志)

作者: supremecsp | 来源:发表于2022-10-27 19:06 被阅读0次

    持久化对于任何数据库来说都是重要的知识点。很久前写过mongo和mysql的日志。今天记录下redis的日志设计。

    Redis把后端数据库中的数据存储在内存中,然后直接从内存中读取数据,响应速度会非常快。但服务器宕机时,内存中的数据将全部丢失。如果从后端数据库(比如:mysql)恢复这些数据,那会出现大的缓存穿透,会给数据库带来巨大的压力;而且从慢速数据库中读取性能肯定比不上从 Redis 中读取,导致使用程序响应变慢。所以,对 Redis 来说,实现数据的持久化很重要。
    目前,Redis 的持久化主要有两大机制,即 AOF(Append Only File)日志和 RDB 快照。

    写后日志:AOF
    Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:


    传统数据库的日志,例如 redo log(重做日志),记录的是修改后的数据,而 AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。
    我们以 Redis 收到“set testkey testvalue”命令后记录的日志为例,看看 AOF 日志的内容。其中,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。

    写后日志的好处:
    1,为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。写后日志这种方式保证了写入日志的命令都是合法的。
    2,是在命令执行后才记录日志,所以不会阻塞当前的写操作。

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

    write 和 fsync
    write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘;
    fsync 需要把日志记录写回到磁盘后才能返回,时间较长。


    当写回策略配置为 everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。
    always 策略并不使用后台子线程来执行。

    落盘策略性能比较
    Always可以做到基本不丢数据(执行瞬间立刻宕机,还未落盘还是会丢失数据),缺点:每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能;

    AOF 重写机制

    AOF 是以文件的形式在记录接收到的所有写命令。随着时间推移,AOF 文件会不断膨胀。需要注意 AOF 文件过大带来的性能问题。(如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢),这时候就需要用到AOF 重写机制。

    重写机制通过“多变一”(旧日志文件中的多条命令,在重写后的新日志中变成了一条命令)的方法,缩小 AOF 文件。


    什么时候会触发AOF 重写?
    有两个配置项在控制AOF重写的触发时机:
    1, auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
    2, auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。
    AOF文件大小同时超出上面这两个配置项时,会触发AOF重写。

    AOF重写对主线程的影响

    和 AOF日志落盘不同,重写过程是由后台子进程 bgrewriteaof 来完成的。这个过程并不会阻塞主线程。

    重写的过程总结为“一个拷贝,两处日志”。
    一个拷贝
    每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存映射拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
    两处日志
    旧的AOF日志:因为主线程未阻塞,仍然可以处理新来的操作。Redis 会把这个操作写到它的缓冲区(很快就合到AOF日志中,需要看落盘策略)。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
    新的AOF日志:AOF 重写日志。这个操作也会被写到重写日志的缓冲区。(拷贝完才合到日志中)这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。

    aof缓冲区:是正常使用aof作为数据落地中间地带,所有的数据先到aof缓冲区再到aof文件中。
    aof重写缓冲区: 是aof重写时,redis还要继续接收数据,这个数据就写到aof重写缓冲区,当aof重写ok时,主进程在把aof重写缓冲区的数据写到aof缓冲区,最后fsync到aof文件中。

    AOF重写的时候,子线程会首先拷贝必要的数据结构包括内存页表,完成了这个操作就可以进行重写,只不过父子进程这个时候指向的是同一个内存,在子进程重写过程中若父进程操作了已有的key,则会重新申请新的内存,这样父子进程就逐渐的拥有独自的内存空间。
    总结Linux的Copy On Write技术:
    1,fork出的子进程共享父进程的物理空间,当父子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享父进程的)。
    2,fork出的子进程功能实现和父进程是一样的。如果有需要,我们会用exec()把当前进程映像替换成新的进程文件,完成自己想要实现的功能。
    Copy On Write机制了解一下

    简单来说就是重写是复制一份地址映射,父线程只要改动了,子线程就开辟新的空间,映射修改。然后还记录一份缓冲区缓存日志,等备份完再执行一下缓冲区的日志。

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

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

    当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。所以,如果后台子线程执行的 fsync 频繁阻塞的话(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢。


    如果业务应用对延迟非常敏感,但同时允许一定量的数据丢失,那么,可以把配置项 no-appendfsync-on-rewrite 设置为 yes。这个配置项设置为 yes 时,表示在 AOF 重写时,不进行 fsync 操作。也就是说,Redis 实例把写命令写到内存后,不调用后台线程进行 fsync 操作,就可以直接返回了。

    落盘时机和重写机制都是在“记日志”这一过程中发挥作用的。例如,落盘时机的选择可以避免记日志时阻塞主线程,重写可以避免日志文件过大。但是,在“用日志”的过程中,也就是使用 AOF 进行故障恢复时,我们仍然需要把所有的操作记录都运行一遍。再加上 Redis 的单线程设计,这些命令操作只能一条一条按顺序执行,这个“重放”的过程就会很慢了。
    有没有既能避免数据丢失,又能更快地恢复的方法呢?当然有,那就是 RDB 快照了。

    摘抄:《Redis 核心技术与实战》-第4节

    相关文章

      网友评论

          本文标题:redis(二:日志)

          本文链接:https://www.haomeiwen.com/subject/fskftdtx.html