Redis 持久化

本文将介绍 Redis 中的持久化。

一、Redis 持久化

Redis 会将数据全部存储于内存之中,因此拥有了极高的性能,但这也带来了断电后丢失数据的缺点。

针对这一问题,Redis 提供了数据持久化机制,它能够将内存中的数据保存到本地磁盘中,实现数据的持久存储。

Redis 提供了两种持久化机制:

  • RDB
  • AOF

二、RDB - 快照

1. 说明

RDB,即快照模式,是 Redis 默认的数据持久化方式,它将内存中所有的数据持久化并保存在内存快照文件中。

2. 阻塞与非阻塞

(1) 阻塞式

RDB 支持堵塞方式生成快照,在主线程中执行,将影响 Redis 的正常运行。

(2) 非阻塞式

RDB 模式会将内存中所有的数据一起保存,内存中的数据越多,需要耗费的性能越多,需要耗费的时间越长。

对于持久化,我们希望能做到两点:

  • 不影响 Redis 的正常运行
  • 持久化的数据要和内存中的数据一致,且越及时越好

为此,Redis 提供的的解决方案是:调用 fork() 方法创建子进程,子进程将 “立刻” 获得父进程当前的内存空间,通过子进程进行数据的持久化。

共享内存空间的实现机制是 COW,可以在不影响主进程的情况下获取内存中的所有数据,避免拷贝数据时性能的大量耗费。

具体请看:

编程思想 Copy-on-Write

3. 触发方式

(1) 手动触发 - 阻塞式

1
SAVE

执行 SAVE 命令后,Redis 服务器进程将被阻塞,直到持久化完毕,在此期间 Redis 将不能处理任何请求。

(2) 手动触发 - 非阻塞式

1
2
3
BGSAVE

LASTSAVE

BGSAVE 命令是非阻塞式的,在持久化执行过程中并不会影响 Redis 处理其它请求。

LASTSAVE 用于查看 BGSAVE 是否执行成功。

(3) 自动触发

主动触发策略可以在 Redis 的配置文件中查看和修改,如下:

save m n 的含义是:如果 m 秒内至少发生了 n 次变化,则执行持久化。

这里的持久化是非阻塞式持久化

4. 性能开销

快照的性能开销主要在以下三个方面:

  • fork:fork 操作的 “页表拷贝” 会堵塞主线程,且主线程的内存越大堵塞时间越长
  • 写时拷贝:如果在快照过程中,Redis 进行了写入操作,则需要进行写时拷贝,造成一定的性能开销
  • 写入硬盘:将数据写入硬盘也会带来性能开销

三、AOF 持久化

1. 说明

AOF,又叫追加模式、日志模式,它会将 Redis 中已执行过的命令追加存储到 AOF 日志中。

2. 写入机制

Redis 会在接到客户端指令后,首先执行命令,再将命令追加存储至 AOF 日志中。

为什么不先写日志再执行?

  • 如果先写日志,需要考虑写入前的语法检查、写入失败时的日志项删除,以保证日志和实际数据的一致性;

    如果后写日志,命令执行成功便代表语句没有问题,可以直接记录

  • 如果先写日志再执行,会造成操作的等待

3. 读取机制

Redis 会读取 AOF 日志中的命令,进行 “命令重演”,从而达到恢复数据的目的。

4. 写回策略

(1) 说明

Redis 为了提升效率,不会将 AOF 日志直接写入磁盘中,而是会首先将其放到缓存区中(write),在 “适当时机” 写回磁盘(fsync)。

(2) 写回策略配置

Redis 配置文件中存在配置项如下:

1
2
3
# appendfsync always
appendfsync everysec
# appendfsync no

该项的作用是指定 Redis 何时将数据从缓存区写回磁盘,其中:

  • always:

    • 缓存区中每加入一条命令就写回一次
    • 最慢
    • 使用主线程进行
    • 可以做到基本不丢数据,但是在每个写命令后都有一个慢速的落盘操作,将会影响主线程性能
  • everysec:

    • 每一秒钟写回一次
    • 默认
    • 会使用后台的子线程异步完成
    • 对主线程的性能影响小了很多,但是发生宕机时仍会丢失一秒钟的命令,在性能和数据完整性中取了个折中
  • no:

    • 不主动写回,而是由操作系统决定何时写回
    • 不安全,落盘时机不由 Redis 控制

5. 文件重写

(1) 为什么需要文件重写?

随着 Redis 的不断运行,命令逐渐增多,AOF 日志也势必会越来越大。

AOF 日志文件过大会带来以下几方面的问题:

  • 文件系统本身可能对文件大小有最大限制
  • 向 AOF 日志中继续追加命令时效率会降低
  • 恢复时 “命令重演” 的耗时将会很长

因此,Redis 提供了文件重写功能,用于获取一个更小的 AOF 日志。

(2) 文件重写不是重写!

文件重写是一个有歧义的名字,它并不会对原本的 AOF 文件做任何读入、分析、写入操作,而是会直接读取内存中的内容生成一个完全新的 AOF 日志。

(3) 文件重写怎么做?

  • 主进程 fork() 出 bgrewriteaof 子进程,子进程获得主线程的内存拷贝,其中包含数据库中的最新数据

  • 主进程继续处理请求并维护原有的 AOF 日志

  • 子进程对内存中的数据进行遍历,将数据转化为一系列的操作语句,写入一个新的 AOF 日志中

  • 当子进程写入完成后,向主进程发送信号,要求将 “重写” 期间发生的操作追加到新的 AOF 日志中

  • 用新的 AOF 日志替代原有的 AOF 日志

(4) 手动重写

1
BGREWRITEAOF

(5) 自动重写

配置文件中关于自动重写的配置项如下:

1
2
3
auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

其中,

  • auto-aof-rewrite-percentage:表示超过最小文件体积后,每增大多少进行自动重写
  • auto-aof-rewrite-min-size:表示触发自动重写的最小文件体积

因此,应用上面的配置项时,触发自动重写的时机为:64mb 128mb 256mb ···

四、RDB 和 AOF 的对比

  • RDB 全量保存;

    AOF 增量保存

  • RDB 持久化需要一次备份所有数据,耗费资源且耗费时间;

    AOF 持久化每次只追加少量命令,较为轻量

  • RDB 耗费较大,因此备份不能过于频繁,以免影响服务器的性能;

    AOF 会记录每条命令,并(默认)每秒持久化一次,备份间隔较短

  • RDB 因为备份时间较长、备份间隔较长的原因,宕机会将会丢失一部分最新数据;

    AOF 在默认磁盘写入策略下,最多只会丢失一秒的数据

  • RDB 恢复速度较快;

    AOF 恢复速度较慢

  • RDB 有阻塞和非阻塞两种方式;

    AOF 始终非阻塞

五、RDB 和 AOF 混合使用

在 Redis 4.0 中提出了混合使用 RDB 和 AOF 的方法,简单来说,以一定的频率创建 RDB 内存快照,在两次快照之间,使用 AOF 日志记录所有的命令。

这样的做法有以下几点好处:

  • 可以避免为了不丢失数据而频繁创建 RDB 快照带来的性能开销
  • 可以避免 AOF 日志记录过多内容(现在只需要记录间隔期的少量命令)而出现的文件过大问题
  • 可以尽可能地避免数据的丢失

参考