Redis AOF、RDB

33 阅读8分钟

AOF

  • 只会记录写操作命令,读操作命令不会被记录

  • 执行流程

    • 客户端发送命令给redis
    • redis第一步执行写命令写入内存
    • 第二步记录命令到日志中
    • 例如:
      • set name hyggebest
      • AOF日志为:*3 3set3 set 4 name $9 hyggebest
      • *3(操作分为三部分)、$4 name(字节数)
  • 存储过程

    • 第一步执行客户端的命令,进行操作
    • 第二步写日志,将命令写入AOF的缓冲区,最后才会写入磁盘。(ps:所有的写磁盘操作都不会直接真正写入磁盘中,因为I/O开销大)
  • 写入磁盘(策略)

    • 流程:redis进入第二步将客户端命令写入AOF buffer缓冲区中,然后如果系统调用witer()命令将日志拷贝到内核缓冲区page cache中。最后的真正写入磁盘中由内核决定。
    • reids 可配置策略选项,在redis.conf中的appendfsync
      • Always:接收到客户端的命令之后直接写入,来一个我写一个。执行执行fsync()。
      • Everysec:每秒执行一次将缓冲区中的日志写入磁盘中。因为日志先存在AOF buffer中的,在接收客户端命令的时候先将命令写入缓冲区中的。创建一个异步任务去调用fsync().
      • No:redis永远不会将AOF buffer中的日志写入磁盘的,而是由操作系统决定什么时候写。永远不会调用fsync().

这三种都有一些缺陷吧。Always会有很大的I/O开销,同步写还会影响主进程的性能,但是可以保证数据的完整性;Everysec很大程度上能减少对主进程的性能影响,但是在数据完整性上会丢一些数据,毕竟1s执行一次也就一位如果发生宕机的情况下会丢失1s的数据;No由操作系统决定什么时候写入磁盘的,在数据完整性上无法保证,但是性能好一些。

  • 重写机制

    • 为了避免AOF文件过大,会带来性能问题。如果重启redis的时候文件过大要执行的命令很多,过程就会很慢,这就是为什么会有重写机制。
    • 来看看场景:
      • 用户A注册成功并把昵称注册为madhatter对应写入redisset name madhatter
      • 然后A用户把用户名改成hyggebest对应对应redisset name hyggebest
      • 如果没有重写机制的情况下就会记录着两条命了,但其实madhatter对于AOF日志是没有意义的。
      • 这个时候在使用重写机制的情况下只会读取最新的key-value,这样就只会记录一条记录。这样占用的空间和资源就少很多了。
      • 重写工作完成后,会将新的AOF文件覆盖掉现有的AOF文件,相当于压缩文件,这样文件就小很多。
      • 为什么不使用现有的AOF文件而是使用重写后的AOF文件呢?因为如果AOF文件重写失败了,现有的AOF文件就会造成污染,可能无法用于恢复使用。
  • AOF后台重写

    • 重写时机:当AOF文件大于64MB的时候,会将AOF文件进行重写,这个时候就需要进行重写。需要将缓存中的所有键值对数据取出,并为每个键值对生成一条命令,然后将其写到新的AOF文件,重写完成后就可以替换掉。
    • 重写过程是需要耗时间的,不可能一直阻塞,而是由另外的子进程进行完成。
    • 重写过程:
      • 主进程通过fork系统调用生成bgrewriteaof子进程
      • 操作系统将主进程的也表复制一份给子进程,共享一片物理内存,节省空间。也表对应的也表项的属性会标记该物理内存权限为只读
      • 如果在重写的过程中,子进程或主进程发生内存操作(写操作)的时候,就会触发[写时复制]。写时复制:在发生写操作的时候,操作系统就会去复制物理内存。
      • 一般情况下子进程是不会进行写操作的,在重写的这个过程中,是不会影响主进程正常工作的,因为着可以进行正常的写操作。
      • 如果这个时候主进程有一个修改操作,触发写时复制,操作系统将会将修改的这个key-value对应的内存数据复制一份出来,没有修改的数据父子进程还是共享的。
      • 这个时候就会出现一个问题,主进程修改的key-value,因为触发了写时复制,子进程和父进程的内存数据不一致,这个时候就需要一个buffer缓冲区来解决问题。
      • 当在AOF重写的过程, redis完成一条命令操作之时,同时将写个命令写入AOF缓冲区和AOF重写缓冲区中。
      • 当子进程完成AOF重写工作之后,会向主进程发送一个信号通知主进程,异步进行。
      • 主进程收到信号之后,会调用一个信号处理函数
        • 会将AOF重写缓冲区中的内容追加到新的AOF文件中,使的新旧两个AOF文件的数据状态保持一致
        • 将新的AOF文件进行改名,覆盖掉旧的AOF文件

总结:AOF的存储方式为命名式存储。在写入磁盘的机制有三种,可以通过配置去设置:ALways、Everysec、No三种机制各有优缺点,写入磁盘的时机也不一样。如果AOF文件大小如果大于64MB就会触发重写机制,会进行扫描数据中的所有键值对,对每一条数据生成一条写操作命令,将命令写入新的AOF文件中,重写完成后将新的AOF文件覆盖掉旧的AOF文件。在重写过中,由子进程进行完成。

RDB

  • RDB内容为二进制数据
  • reids通过savebgsave命令来生成RDB文件
    • save由主进程生成RDB文件,由于和执行命令都在同一个线程,如果写入RDB文件时间过长,会造成阻塞
    • bgsave是创建一个子进程来生成RDB文件,这样就不会影响主进程,不会发生阻塞。
  • 命令save time number
    • time秒之内,对数据库进行了至少number次修改。
  • redis的RDB快照是全量快照,也就是说每一次进行RDB快照时候就需要将内存中的所有数据进行记录到磁盘中,如果如果进行频繁的RDB的时候,是一个很重的操作,对性能多多少少都是有影响的。如果我们设置的时间time是3分钟的话,如果发生宕机的情况就会丢失3分钟的数据。
  • 执行快照的过程
    • 这个时候使用bgsave创建一个子进程去进程。
    • 一样也是fork创建子进程,复制主进程的页表给到子进程共享一片内存数据
    • 如果这个时候父进程发生内存修改的操作就会触发写时复制。
    • 发生修改的部分,物理内存会被复制一份。父进行会对这个复制出来的内存进行修改,不会影响到子进程的写入。
    • 发生写时复制的数据为刚刚写入的数据,但是子进程的数据为旧数据,这个时候子进程是没有办法将主进程的新修改的数据进行RDB快照的,只能等下一次RDB快照的时候才行写入。
    • 这里如果宕机的情况的下,就会丢失RDB前的数据

总结:RDB快照为二进制数据进行存储的,可以通过命令bgsave进行创建子进程进行生成RDB快照,在生成RDB快照的过程中如果触发了写时复制的情况,这个时候子进程是没有办法将新的操作记录下来的,也就是会丢失新修改的数据。

RDB AOF 混合模式

  • 配置aof-use-rdb-preamble配置成yes
  • 启用混合模式的情况下,fork出来的子进程会将与主进程的共享内存数据去以RDB方式写入AOF文件中,然后在这个过程中,主线程处理的操作命令会被记录到重写缓冲区中,重写缓冲区的增量命令以AOF方式写入到AOF文件中,这样就可以解决单独RDB会丢失的数据,所以文件中含有AOF和RDB两个内容,然后RDB和AOF会将旧的AOF文件替换掉。
  • 注意前半部分为RDB内容,后半部分为AOF,因为RDB为二进制数据,相对来说快很多。
  • 后半部分的AOF弥补了单独RDB可能丢失的数据。
  • 这样一来性能上来说快很多,丢失的数据也少