【Redis】持久化

35 阅读6分钟

持久化

AOF

  • AOF(Append Only File) :对用户的插入、修改操作进行持久化

    • 组成

      • *数字:当前操作有几个部分
      • $数字+命令、键或值:数字表示命令、键或值一共有多少字节
    • 先执行写操作命令后,才将该命令记录到 AOF 日志

      • 避免额外的检查开销,保证命令是正确的
      • 不会阻塞当前写操作命令的执行
    • 问题:

      • 还没AOF,数据会丢失
      • 可能会给「下一个」命令带来阻塞风险

  • redis.conf配置,默认不开启

写回策略

Why?

持久化到磁盘的时机?

持久化过程:

  1. Redis 执行完写操作命令,将命令追加到 server.aof_buf 缓冲区
  2. write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件
  3. 缓冲区的数据什么时候写入到硬盘?
  • 写回策略

    • Always:每次写操作命令执行完后fsync()
    • Everysec:每隔一秒fsync()
    • No:操作系统决定
  • 主进程阻塞 VS 数据丢失

    • 高性能:No
    • 高可靠:Always
    • 折中:Everysec

重写机制

Why?

AOF 文件越来越大,重启速度越来越慢

How?

重写,压缩AOF文件

  • 读取当前数据库将每一个键值对用一条命令记录到「新的 AOF 文件」,记录完后进行替换。

  • 为什么新开文件?

    • 重写失败,AOF 文件污染
  • 重写时机

    • AOF 文件大于 64M 时

后台重写

Why?

重写过程耗时长,不能主进程处理

How?

  • 后台子进程 bgrewriteaof 进程重写

    • 避免阻塞主进程

    • 子进程带有主进程的数据副本(只读共享内存),重写时无需加锁

      • 父子进程任意一方修改了该共享内存,就会发生「写时复制」

        • 写时复制(Copy On Write):父进程或者子进程写共享的内存,CPU 写保护中断,进行物理内存复制,重新设置内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作
      • 写操作时才会去复制物理内存,防止 fork 创建子进程时,物理内存数据的复制时间过长导致父进程长时间阻塞的问题。

      • 两个阶段会导致阻塞父进程:

        • 创建子进程时:要复制父进程的页表等数据结构,阻塞时间跟页表的大小有关;
        • 创建完子进程后,子进程或者父进程修改了共享数据:发生写时复制,期间会拷贝物理内存,内存越大,阻塞时间越长;

  • 主进程修改了已经存在 key-value:发生写时复制,只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。

    • 修改bigkey:复制的物理内存数据的过程就会比较耗时,有阻塞主进程的风险。
  • 主进程修改已存在 kv,子进程数据不一致:AOF 重写缓冲区

    • 同时将这个写命令写入到 「AOF 缓冲区」和 「AOF 重写缓冲区」

    • 子进程完成后,主进程

      • AOF 重写缓冲区中内容追加新的 AOF

      • 新 AOF 名,覆盖现有的 AOF 文件。

阻塞时机:

  • 写时复制

  • 信号处理函数执行时

RDB

  • AOF:记录操作命令;
  • RDB:记录二进制数据。

Why?

AOF回复要一条条指令执行,速度慢

How?

  • 直接把内存的数据生成一个快照,恢复时可直接加载到内存

  • 两种快照方式:

    • save:在主线程生成 RDB 文件,会阻塞

    • bgsave:子进程生成 RDB 文件避免主线程的阻塞,时机:

      • # 900 秒之内,对数据库进行了至少 1 次修改;
        save 900 1
        # 300 秒之内,对数据库进行了至少 10 次修改;
        save 300 10
        # 60 秒之内,对数据库进行了至少 10000 次修改;
        save 60 10000
        
      • 写时复制技术:主线程修改共享数据,物理内存复制一份,主线程在数据副本进行修改操作。
      • bgsave 子进程可以把原来的数据写入到 RDB 文件。
  • 缺点:

    • 还没快照的数据会丢失(bgsave时修改的数据、后来修改的数据)
    • bgsave时修改过多数据,内存占用变2倍

RDB 结合 AOF

Why?

AOF恢复效率低,RDB丢失数据多

How?

Redis 4.0:混合使用 AOF 日志和内存快照,也叫混合持久化。

aof-use-rdb-preamble yes
  • 混合持久化

    • AOF 重写日志时,副本数据子进程先RDB 方式写入,重写缓冲区数据以 AOF 方式写
    • 写入完成后主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧 AOF 文件
  • 加载速度很快

  • 数据更少丢失

过期键会如何处理的?

RDB:

  • RDB 文件生成阶段:从内存状态持久化成 RDB(文件)时,对 key 进行过期检查,过期的键「不会」被保存到新的 RDB 文件中。

  • RDB 加载阶段:

    • 主服务器:对文件中保存的键进行检查,过期键「不会」被载入到数据库中。
    • 从服务器:不论键是否过期都会被载入到数据库中。但由于主从服务器在进行数据同步时,从服务器的数据会被清空。所以一般来说,过期键对载入 RDB 文件的从服务器也不会造成影响。

AOF:

  • AOF 文件写入阶段:AOF 模式持久化时,如果数据库某个过期键还没被删除,那么 AOF 文件会保留此过期键,当此过期键被删除后,Redis 会向 AOF 文件追加一条 DEL 命令来显式地删除该键值。
  • AOF 重写阶段:AOF 重写时,对 Redis 中的键值对进行检查,已过期的键不会被保存到重写后的 AOF 文件中,因此不会对 AOF 重写造成任何影响。

主从模式

  • 从库不会进行过期扫描,从库对过期的处理是被动的。

  • 主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。

大Key

Why?

写入很多大 Key,AOF 日志文件很大,很快就会触发 AOF 重写机制。

  • fork() 创建子进程时,如果页表很大,复制过程会很耗时,发生阻塞现象。

How?

如果 fork 耗时很大,比如超过1秒,优化调整:

  • 单个实例的内存占用控制在 10 GB 以下,fork 函数能很快返回。

  • Redis 只当作缓存,不关心数据安全性:关闭 AOF 和 AOF 重写,不会调用 fork 函数。

  • 主从架构:适当调大 repl-backlog-size

    • 避免 repl_backlog_buffer 不够大,主节点频繁全量同步(创建 RDB 文件的,调用 fork 函数)

Why?

  • Always 策略:修改大 Key 写时复制,阻塞的时间会比较久

How?

Linux 开启内存大页,会影响 Redis 的性能的

  • 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。
  • 关闭内存大页(默认是关闭的)
echo never >  /sys/kernel/mm/transparent_hugepage/enabled

避免大 Key

  • 设计阶段,就把大 key 拆分成一个一个小 key。

  • 定时检查 Redis 是否存在大 key

    • 该大 key 是可以删除的

      • 不使用 DEL ,该命令删除过程会阻塞主线程,
      • 用 unlink(Redis 4.0+)删除大 key,异步的,不会阻塞主线程。