浅谈 Redis 持久化 - RDB 和 AOF 原理

持久化

什么是持久化

持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。

持久化的实现方式

  • 快照方式持久化:在某时刻把所有数据进行完整备份,例如:MySQL 的 Dump 方式、Redis 的 RDB 方式
  • 写日志方式持久化:把用户执行的所有写指令(增删改)备份到文件中,还原数据时只需要把备份的所有指令重新执行一遍即可,例如:MySQL 的 Binlog、Redis 的 AOF、Hbase 的 HLog

RDB

RDB 介绍

RDB 持久化方式是在指定的时间间隔内将内存中的数据集快照写入磁盘(point-in-time snapshot)。在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。在 Redis 运行时,RDB 程序将当前内存中的数据库快照保存到磁盘文件中,在 Redis 重启动时,RDB 程序可以通过载入 RDB 文件来还原数据库的状态。

RDB 工作原理

当 Redis 需要保存 dump.rdb 二进制文件时,服务器会执行以下操作。整个过程中,Redis 的主进程不进行任何 I/O 操作,这就确保了极高的性能,使 Redis 可以从写时复制(copy-on-write)机制中获益。

  • Redis 单独创建(fork)一个子进程
  • 子进程将内存中的数据集写入到一个临时 RDB 文件中
  • 当子进程完成对临时 RDB 文件的写入时,Redis 用临时 RDB 文件替换旧的 RDB 文件,并删除旧的 RDB 文件

RDB 的三种主要触发机制

save 命令(同步)

save 命令会执行一个同步操作,以 RDB 文件的方式保存所有数据的快照。特别注意,由于 save 命令是同步命令,会占用 Redis 的主进程,若 Redis 的数据量非常大时,save 命令执行速度会非常慢,会阻塞所有客户端的请求。因此很少在生产环境直接使用 save 命令,可以使用 bgsave 命令代替。如果 bgsave 命令的保存数据的子进程发生错误,导致无法备份时,那么用 save 命令保存最新的数据是最后的手段。

redis-rdb-save

bgsave 命令(异步)

Redis 使用 Linux 系统的 fock() 生成一个子进程来将数据库数据保存到磁盘,主进程继续提供服务以供客户端调用。如果操作成功,可以通过客户端命令 LASTSAVE 来检查操作结果。

redis-rdb-bgsave

save 命令与 bgsave 命令对比如下,特别注意,shutdown、slave 命令也会触发数据快照的创建

redis-save-vs-bgsave

自动生成 RDB 文件

除了手动执行 save 和 bgsave 命令实现 RDB 持久化以外,Redis 还提供了自动自动生成 RDB 文件的方式。通过配置文件对 Redis 进行设置,让它在 “N 秒内数据集至少有 M 个改动” 这一条件被满足时,自动进行数据集保存操作。比如说,以下设置会让 Redis 在满足 “60 秒内有至少有 1000 个键被改动” 这一条件时,自动进行数据集保存操作:

1
save 60 1000

redis-rdb-auto

RDB 相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# RDB自动持久化规则

# 当 900 秒内有至少有 1 个键被改动时,自动进行数据集保存操作
save 900 1

# 当 300 秒内有至少有 10 个键被改动时,自动进行数据集保存操作
save 300 10

# 当 60 秒内有至少有 10000 个键被改动时,自动进行数据集保存操作
save 60 10000

# RDB持久化文件名
dbfilename dump-<port>.rdb

# 数据持久化文件存储目录
dir /var/lib/redis

# bgsave发生错误时是否停止写入,通常为yes
stop-writes-on-bgsave-error yes

# rdb文件是否使用压缩格式
rdbcompression yes

# 是否对rdb文件进行校验和检验,通常为yes
rdbchecksum yes

RDB 的优点

  • RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份,比如可以在每个小时保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题也可以根据需求恢复到不同版本的数据集,非常适合做冷备
  • RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复
  • RDB 在保存 RDB 文件时,父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 I/O 操作,所以 RDB 持久化方式可以最大化地提高 Redis 的性能
  • 若需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么 RDB 方式要比 AOF 方式的数据恢复速度更快

RDB 的缺点

  • 耗时、耗性能:RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在毫秒级内不能响应客户端的请求。如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况可能会持续数毫秒或者几秒,AOF 也需要 fork,但可以调节重写日志文件的频率来提高数据集的耐久度
  • 不可控、丢失数据:如果希望在 Redis 意外停止工作(例如机房断电)的情况下尽量减少数据的丢失,那么 RDB 不适合这种场景。虽然可以配置不同的 save 时间点(例如每隔 5 分钟且对数据集有 100 个写的操作时进行备份),但 Redis 要完整的保存整个数据集是一个比较繁重的工作,通常会每隔 5 分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,可能会丢失几分钟的数据。简单来说,最后一次 RDB 持久化后的数据可能会丢失。

AOF

AOF 介绍

快照功能(RDB)并不是非常耐久(durable),如果 Redis 因为某些原因而造成故障停机,那么服务器将丢失最近写入且仍未保存到快照中的那些数据。 从 1.1 版本开始,Redis 增加了一种完全耐久的持久化方式,那就是 AOF 持久化。AOF 以日志的形式来记录每个写操作,将 Redis 执行过的所有写指令记录下来,同时只许追加文件不能改写文件。在配置文件中启用 AOF(如下配置) 后,每当 Redis 执行一个改变数据集的命令时(比如 SET),这个命令就会被追加到 AOF 文件的末尾。这样的话,当 Redis 重新启时,程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。

1
appendonly yes

AOF 运行原理

AOF 运行原理(创建与恢复)如下:

redis-aof-process

AOF 持久化的三种同步策略

可以通过配置文件配置 Redis 多久才将命令 fsync 到磁盘一次,Redis 提供了以下三种策略。

  • always:每次有新命令需要追加到 AOF 文件时就执行一次 fsync 操作,非常慢,也非常安全

redis-aof-always

  • everysec:每秒 fsync 一次,速度足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据,推荐(并且也是默认)的配置为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

redis-aof-everysec

no:从不 fsync,将数据交给操作系统来处理,由操作系统来决定什么时候同步数据,速度更快,但也更不安全

redis-aof-no

always、everysec、no 三种策略的对比如下:

redis-aof-strategy

AOF 重写

AOF 重写介绍

因为 AOF 的运作方式是不断地将命令追加到文件的末尾,所以随着写入命令的不断增加,AOF 文件的体积也会变得越来越大。举个例子,如果对一个计数器调用了 100 次 INCR ,那么仅仅是为了保存这个计数器的当前值,AOF 文件就需要使用 100 条记录(entry)。然而在实际上,只使用一条 SET 命令已经足以保存计数器的当前值了,其余 99 条记录实际上都是多余的。为了处理这种情况, Redis 支持一种有趣的特性,可以在不打断服务客户端的情况下,对 AOF 文件进行重建(rebuild)。执行 bgrewriteaof 命令,Redis 将生成一个新的 AOF 文件,这个文件包含重建当前数据集所需的最少命令。Redis 2.2 需要自己手动执行 bgrewriteaof 命令,而 Redis 2.4 之后则可以通过配置自动触发 AOF 重写。通过 AOF 重写,可以减少磁盘占用量、加速数据恢复,AOF 重写的对比图如下:

redis-aof-rewrite

AOF 重写的实现方式

bgrewriteaof 命令

Redis 的 bgrewriteaof 命令用于异步执行一个 AOF(Append Only File)文件重写操作,重写后会创建一个当前 AOF 文件的体积优化版本。即使 bgrewriteaof 命令执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 bgrewriteaof 命令执行成功之前不会被修改。AOF 重写操作由 Redis 自行触发,bgrewriteaof 命令仅仅用于手动触发 AOF 重写操作,整个流程如下:

redis-aof-rewrite-process

  • 如果一个子进程是 Redis 通过磁盘快照创建的,那么 AOF 重写将会在 RDB 操作终止后才开始保存,这种情况下 bgrewriteaof 命令依然会返回 OK 状态码。从 Redis 2.6 起,可以通过 info 命令查看 AOF 重写的执行情况
  • 如果正在执行的 AOF 重写操作返回了一个错误,那么 AOF 重写将会在稍后一点的时间重新执行

AOF 重写配置

redis-aof-rewrite-setting

AOF 重写自动触发的条件

Redis 支持 AOF 重写自动触发机制,无需手动执行 bgrewriteaof 命令,但需要同时满足下面两个条件才会自动触发:

  • aof_current_size > auto-aof-rewrite-min-size
  • (aof_current_size - aof_base_size) * 100 / aof_base_size > auto-aof-rewrite-percentage
1
2
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100

假设 Redis 的配置如上,当 AOF 文件的体积大于 64Mb,并且 AOF 文件的体积比上一次重写时的体积大了至少一倍(100%)时,Redis 将执行 bgrewriteaof 命令进行重写。

AOF 重写的流程

Redis 首先 fork 子进程,子进程开始将新 AOF 文件的内容写入到临时文件。对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样即使在重写的中途发生宕机,现有的 AOF 文件也还是安全的。当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。最后 Redis 原子地用新 AOF 文件替换旧 AOF 文件,之后所有命令都会直接追加到新 AOF 文件的末尾。

redis-aof-rewrite-process-2

AOF 相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 开启AOF持久化方式
appendonly yes

# AOF持久化文件名
appendfilename appendonly-<port>.aof

# 每秒把缓冲区的数据同步到磁盘
appendfsync everysec

# 数据持久化文件存储目录
dir /var/lib/redis

# 是否在执行重写时不同步数据到AOF文件
no-appendfsync-on-rewrite yes

# 触发AOF文件执行重写的最小尺寸
auto-aof-rewrite-min-size 64mb

# 触发AOF文件执行重写的增长率
auto-aof-rewrite-percentage 100

AOF 的优点

  • 使用 AOF 会让 Redis 更加耐久:可以使用不同的 fsync 策略:无 fsync、每秒 fsync、每次写的时候 fsync。使用默认的是每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台子进程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,最多丢失 1 秒的数据
  • AOF 对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现磁盘空间已满、宕机等现象,也不会破坏日志文件中已经存在的内容。而且如果本次操作只是写入了一半数据就出现了系统崩溃问题,也不用担心,在 Redis 下一次启动之前,可以通过 redis-check-aof 工具来修复数据一致性问题
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 文件进行重写,重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生宕机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作
  • AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单。举个例子,如果不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态

AOF 的缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积,而且 RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。AOF 开启后支持写的 QPS 会比 RDB 支持的写的 QPS 低,但在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和 RDB 一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)

修复错误的 AOF 文件

服务器可能在 Redis 正在对 AOF 文件进行写入时宕机,如果宕机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件,从而确保数据的一致性不会被破坏。当发生这种情况时,可以用以下方法来修复出错的 AOF 文件:

  • 为现有的 AOF 文件创建备份文件
  • 使用 Redis 附带的 redis-check-aof 工具,对原来的 AOF 文件进行修复
1
$ redis-check-aof --fix
  • 使用 diff -u 命令对比修复后的 AOF 文件和原始的 AOF 备份文件,查看两个文件之间的不同之处,此步骤为可选操作
  • 重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复

RDB 和 AOF 对比

redis-rdb-aof

如何选择使用哪种持久化方式?

  • 如果非常关心数据的安全性,但仍然可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化
  • 不推荐只使用 AOF 持久化,因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快
  • 如果想达到足以媲美 PostgreSQL 的数据安全性,则应该同时使用两种持久化机制。当同时使用 RDB 和 AOF 两种持久化机制时,那么在 Redis 重启的时候,会优先使用 AOF 来重建数据集,因为 AOF 的数据更加完整

总结,生产环境中推荐同时使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为恢复数据的第一选择;用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,可以使用 RDB 进行快速的数据恢复。