(四)Redis 持久化

前言

  由于 Redis 是内存数据库,因此会将自己的数据库状态存储在内存当中,但是,一旦服务器进程出现意外退出了,若不想办法将存储在内存中的数据库状态保存到磁盘中,则服务器中的数据库状态也会消失不见。
  为了避免数据的意外丢失,需要将内存中的数据库状态保存到磁盘中
  为此,Redis 提供了持久化技术以达到该目的。

  在 Redis 中,持久化拥有以下三种方式:

  • RDB(Redis DataBase):快照方式,将某一个时刻的内存数据,以二进制的方式写入磁盘;
  • AOF(Append Only File):文件追加方式,记录所有的操作命令,并以文本的形式追加到文件中;
  • 混合持久化方式:Redis 4.0 之后新增的方式,其结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

  下面,就跟随本文来了解一下这三种方式吧!

RDB

  RDB 通过快照形式,可将某个时间点上的数据库状态压缩保存到一个二进制文件中,需要时 Redis 可还原数据库状态为生成此文件时的数据库状态。

  由于 RDB 文件是保存在硬盘中的,因此即使 Redis 服务器进程退出,甚至运行 Redis 服务器的计算机停机,但只要 RDB 文件仍然存在,Redis 服务器就可以用它来还原数据库状态。

作用时间点

  RDB 文件是如何起作用的呢?
  从设计层面而言,要想保证数据安全,应当将数据保存下来,在需要的时候通过保存的文件进行数据恢复工作,因此,我们只需要关注两个核心问题:

  • Redis 服务器是在什么时候创建 RDB 文件的
  • Redis 服务器又是在什么阶段需要通过 RDB 文件进行数据恢复的

RDB 文件的创建

  RDB 文件在何时创建的呢?
  在关注何时之前,我们先了解下 Redis 中是如何创建 RDB 文件的。

生成 RDB 文件的方式

  在 Redis 中,存在两个命令用于生成 RDB 文件:

  • SAVE
  • BGSAVE
SAVE

  SAVE命令可用于生成 RDB 文件,但是它会阻塞服务器进程,直到 RDB 文件创建完毕为止
  因此,在服务器进程阻塞期间,服务器不能处理任何客户端发送的命令请求,只有在服务器执行完SAVE命令后、重新开始接受命令请求之后,客户端发送的命令才会被处理。

1
2
127.0.0.1:6379> save    // 一直等待,直到 RDB 文件创建完毕
OK

  由于SAVE的阻塞特性,就出现了不阻塞的BGSAVE命令。

BGSAVE

  与SAVE命令不同,BGSAVE命令会 fork(派生)出一个子进程,然后由子进程负责创建 RDB 文件
  在此期间,服务器进程(父进程)将继续处理命令请求,因此 Redis 服务器仍可继续处理客户端的命令请求。

1
2
127.0.0.1:6379> bgsave    // 派生一个子进程,并由子进程创建 RDB 文件
Background saving started

工作原理

  BGSAVE命令的fork采用的是copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作

  因此,若子进程效率太低,主进程拷贝的数据增多的情况下,会加大内存负担,毕竟数据存了双份嘛。

注意点
  • BGSAVE命令不能和BGREWRITEAOF命令同时执行
  • 为了防止产生产生竞争条件,BGSAVE命令执行期间,客户端发送的SAVE命令和BGSAVE命令都会被服务器拒绝

RDB 文件的创建时间

  刚刚我们知道了 生成 RDB 文件的命令,但有个问题,我们总不可能一直去服务器手动执行对应命令吧?
  确实如此,那应该要搞成自动保存的方式呀!但是自动保存的时间点又是个令人纠结的问题,这可如何是好?

自动间隔性保存

  不用纠结,Reds 已经帮我们考虑好了:因为BGSAVE命令可以在不阻塞服务器进程的情况下执行,所以 Redis 允许用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。
  用户可以通过save选项设置多个保存条件,只要其中任意一个条件被满足,服务器就会执行BGSAVE命令。
  以下为redis.conf文件中相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""

save 900 1
save 300 10
save 60 10000

  既然配置了这些参数,那它们是如何与BGSAVE命令进行关联的呢?
  其实,在 Redis 的服务器中,存在一个默认每隔 100 毫秒执行一次的周期性操作函数serverCron,该函数用于对正在运行的服务器进行维护,它的一项工作就是检查上面save选项所设置的保存条件是否已经满足,若满足的话,就执行BGSAVE命令。

  因此,只要满足配置中的 3 个条件中的任意一个保存条件,BGSAVE命令就会被执行,翻译一下就是:

  • 服务器在 900 秒之内,对数据库进行了至少 1 次修改
  • 服务器在 300 秒之内,对数据库进行了至少 10 次修改
  • 服务器在 60 秒之内,对数据库进行了至少 10000 次修改

  当然,以默认方式启动服务器的保存配置并不一样,默认如下:

1
2
3
127.0.0.1:6379> config get save
1) "save"
2) "3600 1 300 100 60 10000"

  从前面我们知道,RDB 文件是按设置的时间内命令执行达到设置的次数后进行保存的,所以默认配置理解起来就是:

  • 服务器在 3600 秒之内,对数据库进行了至少 1 次修改
  • 服务器在 300 秒之内,对数据库进行了至少 100 次修改
  • 服务器在 60 秒之内,对数据库进行了至少 10000 次修改

RDB 文件的载入

  由于 Redis 是内存数据库,当服务器停止,内存中的数据将丢失,为了恢复内存中的数据,在 Redis 运行期间生产了 RDB 文件用于保存数据,因此 Redis 服务器重启时只要重新载入 RDB 文件就可以恢复原先的数据。

自动载入

  由于 RDB 文件的载入工作是在服务器启动时自动执行的,所以 Redis 并没有专门用于载入 RDB 文件的命令,只要 Redis 服务器在启动时监测到 RDB 文件存在,它就会自动载入 RDB 文件。
  下面为启动服务器时的日志:

1
2
3
4831:M 08 Apr 2019 10:46:15.806 # Server initialized
4831:M 08 Apr 2019 10:46:15.806 * DB loaded from disk: 0.000 seconds //载入RDB文件
4831:M 08 Apr 2019 10:46:15.806 * Ready to accept connections

载入的 RDB 文件路径

  根据不同的启动方式,载入的 RDB 文件也不相同:

  • 默认方式启动时,将载入启动脚本对应目录下的dump.rdb文件
  • 以指定配置文件方式启动时,服务器载入的是配置文件中指定 RDB 存放路径下dump.rdb文件

  在生产环境中,一般会专门配置一个数据目录,专门存储 Redis 的数据文件。

  在配置文件中,dir属性用于配置 RDB 文件的存储路径:

1
2
# 默认生成在启动脚本的目录下
dir ./

注意点

  Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

文件格式

  打开dump.rdb文件,将显示以下内容:

1
2
REDIS0009�  redis-ver�5.0.4�
redis-bits�@��ctime¾�\��used-mem���� aof-preamble���i����=y

  显而易见,这是一堆有部分是看不懂的二进制字符,因为 RDB 文件本身就是是一个经过压缩的二进制文件。   

  默认情况下,RDB 文件就具有一定字节,保存了 Redis 版本等信息。

1
2
3
4
5
6
7
8
od dump.rdb
0000000 042522 044504 030123 030060 175071 071011 062145 071551
0000020 073055 071145 032405 030056 032056 005372 062562 064544
0000040 026563 064542 071564 040300 002772 072143 066551 141145
0000060 164447 056252 004372 071565 062145 066455 066545 110302
0000100 010007 175000 060414 063157 070055 062562 066541 066142
0000120 140145 177400 014442 141640 160140 073510
0000134

特殊操作对 RDB 文件的影响

停机时

  Redis 服务器手动关闭的时候,会自动触发一次 RDB 持久化功能(使用save 命令)。

flushall 对 RDB 文件的影响

  当 Redis 执行了flushall命令之后,也触发自动持久化,其会把 RDB 文件清空。

主从复制对 RDB 文件的影响

  在后面要讲的 Redis 主从复制中,当从节点执行全量复制操作时,主节点也会执行bgsave命令,并将 RDB 文件发送给从节点,该过程也自动触发了 RDB 持久化功能。

AOF

  由于 RDB 持久化有一定的时间间隔,因此若在这个时间段内 Redis 服务意外终止了,将造成最新的数据全部丢失。

  为此,Redis 还提供了 AOF 方式支持持久化。

  与 RDB 通过快照方式记录数据库状态不同,AOF 是通过追加保存 Redis 服务器所执行的写命令来记录数据库状态的,若想还原服务器关闭之前的数据库状态,服务器只要读入并重新执行一遍 AOF 文件里面保存的写命令即可。

前置条件:开启 AOF

  与默认开启的 RDB 持久化功能不同,在 Redis 服务器中,默认是关闭 AOF 持久化功能的。

  要想开启 AOF 持久化,存在以下两种方式:

  • 执行命令:config set appendonly yes
  • 修改redis.conf配置文件:将默认的appendonly no改为appendonly yes

作用时间点

  AOF 文件是如何起作用的呢?
  从设计层面而言,要想保证数据安全,应当将数据保存下来,在需要的时候通过保存的文件进行数据恢复工作,因此,我们只需要关注两个核心问题:

  • Redis 服务器是在什么时候创建 AOF 文件的
  • Redis 服务器又是在什么阶段需要通过 AOF 文件进行数据恢复的

AOF 文件的创建

  若想创建一个 AOF 文件,需要将数据保存到 AOF 文件中,那么这个过程是怎样的呢?
  具体而言,此过程可分为以下三个步骤.:

  • 命令追加(append)
  • 文件写入
  • 文件同步(sync)

命令追加

  当 AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。

文件写入

  Redis 的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接受客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serveerCron函数这样需要定期运行的函数。

  为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区中,等到缓冲区的空间被填满,或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面

  因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区的内容写入和保存到 AOF 文件里面,它的行为由redis.conf设置的appendfsync属性来决定,配置如下。

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
26
# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no

  其中的appendfsync属性, 代表 AOF 文件的保存模式,取值存在 3 个:

  • always:服务器在每个事件循环后都要将写入进aof_buf缓冲区中的所有数据同步到 AOF 文件。虽然其效率是 3 个值中最慢的一个,但从安全行来说,它是最安全的,因为即使出现故障停机,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。
  • everysec:默认模式,服务器每隔一秒将写入进aof_buf缓冲区中的所有数据同步到 AOF 文件。从效率上来说,该模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。
  • no:服务器在每个事件循环后将所有数据写入aof_buf缓冲区中,至于何时同步 AOF 文件,则由操作系统控制。因为该模式无需隔时间同步操作,所以写入速度是最快的。不过因为会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长是最长的。若服务机出现故障时,将丢失上次同步之后的所有写命令数据。

写入 = 数据进内存缓冲区, 同步 = 缓冲区数据进硬盘

文件同步(待研究)

  虽然写入时的缓冲区机制提高了效率,但也带来了安全问题。若计算机停机,那么保存在内存缓冲区里面的写入数据将会丢失。

  为此,系统提供了fsyncfdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

AOF 文件的载入

  由于 Redis 是内存数据库,当服务器停止,内存中的数据将丢失,为了恢复内存中的数据,在 Redis 运行期间会追加数据到 AOF 文件用于保存数据,因此 Redis 服务器重启时只要重新载入 AOF 文件就可以恢复原先的数据。

文件格式

1
2
3
4
5
6
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> incr age
(integer) 19
127.0.0.1:6379> incr age
(integer) 20

  当执行完上面命令后,会生成一个appendonly.aof文件,该文件的所有命令都是以 Redis 的命令请求协议格式保存的,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
*2
$6
SELECT
$1
0
*3
$3
set
$3
age
$2
18
*2
$4
incr
$3
age
*2
$4
incr
$3
age

  该文件中,除了用于指定数据库的SELECT命令是服务器自动添加的,其他命令都是之前执行的命令。
  服务器启动时,会载入 AOF 文件并执行其中保存的命令来还原服务器关闭之前的数据库状态。

文件重写机制

  由于 AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF 文件中的内容会越来越多,文件的体积也越来越大。若不加以控制,可能对 Redis 服务器、甚至整个宿主机造成影响,另外,随着文件体积增大,进行数据还原的所需的时间也会变长。

  为此,Redis 提供了 AOF 文件重写(rewrite)功能,类似于压缩。通过该功能,Redis 服务器可以创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个文件保存的数据库状态相同,但新的体积更小。

  我们前面的例子已经创建了一个 AOF 文件,当再执行重写命令bgrewriteaof后:

1
127.0.0.1:6379> bgrewriteaof

  appendonly.aof变为如下格式:

1
2
REDIS0009�  redis-ver5.0.4�
redis-bits�@�ctime�m2�\�used-mem�P� aof-preamble���age����m1��ͧ

  我们使用的是后台重写,原因和 RDB 文件保存和后台保存类似,不仅可以防止线程被长时间阻塞,还可以避免使用锁来保证数据的安全性。
  可是在重写期间,若服务器进程还继续处理命令请求,新命令可能对现有数据库进行更改,会使得服务器当前状态和重写后的状态不一致,这又怎么办呢?
  为了解决数据不一致问题,Redis 服务器设置了一个 AOF 重写缓冲区,在服务器创建子进程之后使用,当 Redis 服务器执行完一个写命令后,会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区,保证数据的一致性,如下图:

AOF 重写缓冲区

安全性疑问

:既然 AOF 文件是纯文本格式,有一定格式,那能否在其上增加或删除一些命令来篡改原文件呢?
:并不能,Redis 存在了一定的检验机制,当发现该文件出现了错误文本时,会提示文件错误,请求错误修复。因此若你按其指定格式来增加命令,该命令会被识别为错误命令,不会执行该命令且报错。

RDB VS AOF

  因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以在 Redis 启动加载数据文件时:

  • 若只开启了 AOF 持久化,Redis 启动时只会加载 AOF 文件进行数据恢复;
  • 若只开启了 RDB 持久化,Redis 启动时只会加载 RDB 文件进行数据恢复
  • 若同时开启了 RDB 和 AOF 持久化,Redis 启动时只会加载 AOF 文件进行数据恢复,此情况下即使 AOF 文件不存在,只有 RDB 文件,也不会加载 RDB 文件
指标\种类 RDB AOF
持久化方式 定时对整个内存做快照 记录每一次执行的命令
数据完整性 不完整,两次备份之间会丢失 相对完整,取决于刷盘策略
文件大小 会有压缩,文件体积小 记录命令,文件体积很大
宕机恢复速度 很快
数据恢复优先级 低,因为数据完整性不如 AOF 高,因为数据完整性更高
系统资源占用 高,大量CPU和内存消耗 低,主要是磁盘 IO 资源,但 AOF 重写时会占用大量 CPU 和内存资源
使用场景 追求更快的启动速度,可以容忍数分钟的数据丢失 对数据安全性要求较高

RDB 优点

  • 节省磁盘空间
  • 恢复速度快

RDB 缺点

  • 虽然 Redis 在 fork 时使用了写时拷贝技术,但若数据庞大时会比较消耗性能
  • 由于一定时间间隔做一次备份,若服务器意外宕机,就会丢失最后一次快照后的所有修改

AOF 优点

  • 备份机制更稳定,丢失数据概率更低
  • 可读性强的日志文本,可以处理误操作

AOF 缺点

  • 耗费更多磁盘空间:对于相同的数据集来说,AOF 文件要比 RDB 文件更大
  • 每次读写都进行同步操作,对服务器有一定性能的压力

混合持久化

  RDB 和 AOF 持久化各有利弊:

  • RDB 可能会导致一定时间内的数据丢失
  • AOF 由于文件较大则会影响 Redis 的启动速度

  为了能同时使用 RDB 和 AOF 各种的优点,Redis 4.0 之后新增了混合持久化的方式。

  在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 文件的格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式化追加的文件的末尾。

  因此,此时重写的 AOF 文件由两种格式的数据([RDB file][AOF tail])组成。

开启混合持久化

  在稳定的 Redis 6.2.6 版本中,若开启了 AOF 持久化功能,混合持久化功能也自动开启了,配置文件中对应的属性值如下:

1
2
# 是否开启混合持久化
aof-use-rdb-preamble yes

数据恢复

  混合持久化的数据恢复和 AOF 持久化过程是一样的,只需要把appendonly.aof文件存放放到 Redis 的根目录,在服务启动时,只要开启了 AOF 持久化,Redis 就会自动加载并恢复数据。

工作流程

  混合持久化的加载流程如下:

  • ① 判断是否开启 AOF 持久化,开启继续执行后续流程,未开启执行加载 RDB 文件的流程;
  • ② 判断appendonly.aof文件是否存在,文件存在则执行后续流程;
  • ③ 判断 AOF 文件开头是否位 RDB 的格式,
    • 若是,先加载 RDB 内容再加载剩余的 AOF 内容
    • 若不是,直接以 AOF 格式加载整个文件

最佳实践

  虽然持久化功能保证了数据不丢失,但同时也拖慢了 Redis 的运行速度,那么,如何更合理的使用 Redis 的持久化功能呢?

  笔者收集了以下几种方案:

  • 不开启持久化
  • 启用混合持久化
  • 集群部分启用持久化

不开启持久化

  根据实际的业务情况考虑,若业务对数据的丢失不敏感的情况下,可考虑关闭 Redis 的持久化,让 Redis 完全基于内存运行,效率最高。

启用混合持久化

  若一定要使用持久化功能,最好使用混合持久化功能,其结合了 RDB 和 AOF 的优点。

集群部分启用持久化

  集群模式部署时,可以部分开启持久化功能,区分主机从机,一台用于响应主业务,一台用于数据持久化,这样可让 Redis 更加高效的运行。

  具体策略:

  • master:不开启,让 master 达到最佳性能
  • slave:开启 AOF 混合持久化(若对数据安全要求不高,可开启 RDB 关闭 AOF)

参考

文章信息

时间 说明
2019-04-13 初稿
2022-10-26 部分重构
0%