superleeyom/blog

5分钟快速理解Redis的持久化

superleeyom opened this issue · 0 comments

Redis 的持久化指的是把内存中存储的数据以文件形式存储到硬盘上,而服务器也可以根据这些文件在系统停机之后实施数据恢复,让服务器的数据库重新回到停机之前的状态。为了满足不同的持久化需求,Redis 提供了RDB持久化AOF持久化RDB-AOF混合持久化等多种持久化方式以供用户选择。如果用户有需要,也可以完全关闭持久化功能,让服务器处于无持久化状态

RDB持久化

RDB 的全称是 Redis DataBaseRDB持久化是 Redis 默认使用的持久化功能,通过创建以.rdb后缀结尾的二进制文件,该文件包含了服务器在各个数据库中存储的键值对数据等信息。Redis 提供了三种创建 RDB 文件的方法,分别是:手动执行SAVE命令、手动执行BGSAVE命令、通过配置选项自动创建等三种方式。

那 RDB 文件的结构是咋样的呢?它由如下几部分组成:

结构 解释
RDB 文件标识符 文件最开头的部分为 RDB 文件标识符,这个标识符的内容为"REDIS"这5个字符。Redis 服务器在尝试载入 RDB 文件的时候,可以通过这个标识符快速地判断该文件是否为真正的 RDB 文件。
版本号 版本号是一个字符串格式的数字,长度为4个字符。新版 Redis 服务器总是能够向下兼容旧版 Redis 服务器生成的 RDB 文件。比如,生成第9版 RDB 文件的 Redis 5.0 既能够正常读入由 Redis 4.0 生成的第8版 RDB 文件。
设备附加信息 记录了生成 RDB 文件的 Redis 服务器及其所在平台的信息,比如服务器的版本号、宿主机器的架构、创建 RDB 文件时的时间戳、服务器占用的内存数量等。
数据库数据 记录了 Redis 服务器存储的0个或任意多个数据库的数据,各个数据库的数据将按照数据库号码从小到大进行排列,每个数据库里面存放的是键值对数据。
Lua 脚本缓存 如果 Redis 服务器启用了复制功能,那么服务器将在 RDB 文件的 Lua 脚本缓存部分保存所有已被缓存的 Lua 脚本。这样一来,从服务器在载入 RDB 文件完成数据同步之后,就可以继续执行主服务器发来的 EVALSHA 命令了。
EOF 用于标识 RDB 正文内容的末尾,它的实际值为二进制值 0xFF。
CRC64校验和 RDB文件的末尾是一个以无符号64位整数表示的 CRC64 校验和,用于校验 RDB 文件是否有出错或损坏的情况。

Redis 服务器载入 RDB 文件的整体流程:打开 RDB 文件 --> 检查文件头 --> 检查版本号 --> 读取设备信息 --> 重建数据库 --> 重建脚本缓存 --> 对比校验和 --> 数据载入完毕。

SAVE命令

可以通过SAVE命令,以同步方式创建出一个记录了服务器当前所有数据库数据的 RDB 文件。SVAE命令是一个无参数命令,创建成功后,返回 OK 提示:

redis> SAVE
OK

由于是同步方式进行创建的 RDB 文件,那在SAVE命令执行期间,Redis 服务器将阻塞,直到RDB文件创建完毕为止。如果 Redis 服务器在执行SAVE命令时已经拥有了相应的 RDB 文件,那么服务器将使用新创建的 RDB 文件代替已有的 RDB 文件。

BGSAVE命令

BGSAVE其实就是解决SAVE命令的阻塞问题的,它与SAVE命令的不同之处在于,BGSAVE命令是异步执行的,BGSAVE不会直接使用 Redis 服务器进程创建 RDB文件,而是使用子进程创建 RDB 文件。

redis> BGSAVE
Background saving started

用户执行BGSAVE命令,Redis 的整个执行流程如下:

  1. 创建一个子进程
  2. 子进程执行SAVE命令,创建新的RDB文件
  3. RDB 文件创建完毕后,子进程退出,并通知Redis服务的主进程,新的 RDB 文件已创建完毕
  4. Redis 服务器进程使用新的 RDB 文件替换已有的 RDB 文件

虽然说BGSAVE命令是异步执行的,Redis 服务器在BGSAVE命令执行期间仍然可以继续处理其他客户端发送的命令请求,不会阻塞服务器,但由于执行BGSAVE命令需要创建子进程,所以父进程占用的内存数量越大,创建子进程这一操作耗费的时间也会越长,因此 Redis 服务器在执行BGSAVE命令时,仍然可能会由于创建子进程而被短暂地阻塞。

通过配置选项自动创建RDB文件

除了手动执行BGSAVESAVE命令创建RDB文件外,Redis 还支持通过配置选项自动创建 RDB 文件:

save <seconds> <changes>

对于 secondschanges 参数,可以这么理解:如果服务器在 seconds 秒之内,对其包含的各个数据库总共执行了至少 changes 次修改,那么服务器将自动执行一次BGSAVE命令。

当用户向 Redis 服务器提供多个 save 选项,只要满足其中任意一个选项,就会自动执行一次BGSAVE命令。

save 6000 1
save 6000 10
save 6000 100

为了避免因满足条件,而频繁触发执行BGSAVE命令,Redis 服务器在每次成功创建 RDB 文件之后,负责自动触发BGSAVE命令的时间计数器以及修改次数计数器都会被清零并重新开始计数。

RDB 持久化的缺陷

无论用户使用的是SAVE命令还是BGSAVE命令,停机时服务器丢失的数据量将取决于创建 RDB 文件的时间间隔:间隔越长,停机时丢失的数据也就越多。

RDB 持久化是一种全量持久化操作,它在创建 RDB 文件时需要存储整个服务器包含的所有数据,并因此消耗大量计算资源和内存资源,所以用户是不太可能通过增大 RDB 文件的生成频率来保证数据安全的。

从 RDB 持久化的特征来看,它更像是一种数据备份手段而非一种普通的数据持久化手段。为了解决 RDB 持久化在停机时可能会丢失大量数据这一问题,并提供一种真正符合用户预期的持久化功能,Redis 推出 AOF 持久化模式。

AOF持久化

与全量式的 RDB 持久化功能不同,AOF 提供的是增量式的持久化功能,这种持久化的核心原理在于:服务器每次执行完写命令之后,都会以协议文本的方式将被执行的命令追加到 AOF 文件的末尾。这样一来,服务器在停机之后,只要重新执行 AOF 文件中保存的 Redis 命令,就可以将数据库恢复至停机之前的状态,有点像 MySQL 的 binlog

时间 事件 AOF 文件记录的命令
T0 执行命令:SET K1 V1 SELECT 0
SET K1 V1
T1 执行命令:SET K2 V2 SELECT 0
SET K1 V1
SET K2 V3

随着服务器不断地执行命令,被执行的命令也会不断地被保存到 AOF 文件中。其实在实际的 AOF 文件中,命令都是以 Redis 网络协议的方式保存的,比如:

*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nSET\r\n$2\r\nk1\r\n$2\r\nv1\r\n        
*3\r\n$3\r\nSET\r\n$2\r\nk2\r\n$2\r\nv2\r\n

Redis 服务器执行:

  • appendonly yes:开启 AOF 持久化功能,Redis 服务器在默认情况下将创建一个名为appendonly.aof的文件作为AOF 文件。

  • appendonly no:关闭 AOF 持久化功能。

设置 AOF 文件冲洗频率

为了提高程序的写入性能,现代化的操作系统通常会把针对硬盘的多次写操作优化为一次写操作。当程序调用 write 系统对文件进行写入时,系统并不会直接把数据写入硬盘,而是会先将数据写入位于内存的缓冲区中,等到指定的时限到达或者满足某些写入条件时,系统才会执行 flush 系统调用,将缓冲区中的数据冲洗至硬盘。

Redis 向用户提供了appendfsync选项,以此来控制系统冲洗 AOF 文件的频率:

appendfsync <value>

通常有三个可选值,分别是:

  • always:每执行一个写命令,就对 AOF 文件执行一次冲洗操作。如果服务器停机,此时最多只会丢失一个命令的数据,但使用这种冲洗方式将使 Redis 服务器的性能降低至传统关系数据库的水平。
  • everysec:每隔 1s,就对 AOF 文件执行一次冲洗操作。服务器在停机时最多只会丢失 1s 之内产生的命令数据,这是一种兼顾性能和安全性的折中方案。
  • no:不主动对 AOF 文件执行冲洗操作,由操作系统决定何时对 AOF 进行冲洗。服务器在停机时将丢失系统最后一次冲洗 AOF 文件之后产生的所有命令数据,至于数据量的具体大小则取决于系统冲洗 AOF 文件的频率。

所以 Redis 使用everysec作为appendfsync选项的默认值。除非有明确的需求,否则用户不应该随意修改appendfsync选项的值。

AOF 重写

随着服务器不断运行,被执行的命令将变得越来越多,而负责记录这些命令的 AOF 文件也会变得越来越大。与此同时,如果服务器曾经对相同的键执行过多次修改操作,那么 AOF 文件中还会出现多个冗余命令。

SELECT 0
SET msg "hello world! "
SET msg "good morning! "
SET msg "happy birthday! "
SADD fruits "apple"
SADD fruits "banana"
SADD fruits "cherry"
SADD fruits "dragon fruit"
SREM fruits "dragon fruit"
SADD fruits "durian"
RPUSH job-queue 10086

以上命令,重写后最终可以简化为:

SELECT 0
SET msg "happy birthday! "
SADD fruits "apple" "banana" "cherry" "durian"
RPUSH job-queue 10086

为了减少冗余命令,让 AOF 文件保持“苗条”,并提供数据恢复操作的执行速度,Redis 提供了 AOF 重写功能BGREWRITEAOF命令,该命令能够生成一个全新的 AOF 文件,并且文件中只包含恢复当前数据库所需的尽可能少的命令。

与 RDB 持久化的 BGSAVE命令一样,BGREWRITEAOF命令也是一个异步命令,Redis 服务器在接收到该命令之后会创建出一个子进程,由它扫描整个数据库并生成新的 AOF 文件。当新的 AOF 文件生成完毕,子进程就会退出并通知 Redis 服务器(父进程),然后 Redis 服务器就会使用新的 AOF 文件代替已有的 AOF 文件,借此完成整个重写操作。

除了手动执行BGREWRITEAOF命令,也可以通过配置自动触发BGREWRITEAOF命令:

  • auto-aof-rewrite-min-size <value>:选项用于设置触发自动 AOF 文件重写所需的最小 AOF 文件体积,当 AOF 文件的体积大于给定值时,服务器将自动执行BGREWRITEAOF命令,该值的默认值是 64mb。

  • auto-aof-rewrite-percentage <value>:它控制的是触发自动 AOF 文件重写所需的文件体积增大比例。举个例子,如果此值设置为 100,表示如果当前 AOF 文件的体积比最后一次 AOF 文件重写之后的体积增大了一倍(100%),那么将自动执行一次BGREWRITEAOF命令。

AOF 持久化的优缺点

  • 优点:

    • 与 RDB 持久化可能会丢失大量数据相比,AOF 持久化的安全性要高得多:通过使用everysec选项,用户可以将数据丢失的时间窗口限制在 1s 之内。
  • 缺点:

    • 为 AOF 文件存储的是协议文本,所以它的体积会比包含相同数据、二进制格式的 RDB 文件要大得多,并且生成 AOF 文件所需的时间也会比生成 RDB 文件所需的时间更长。
    • 因为 RDB 持久化可以直接通过 RDB 文件恢复数据库数据,而 AOF 持久化则需要通过执行 AOF 文件中保存的命令来恢复数据库(前者是直接的数据恢复操作,而后者则是间接的数据恢复操作),RDB 持久化的数据恢复速度将比 AOF 持久化的数据恢复速度快得多,并且数据库体积越大,这两者之间的差距就会越明显。
    • AOF 重写使用的BGREWRITEAOF命令与 RDB 持久化使用的BGSAVE命令一样都需要创建子进程,所以在数据库体积较大的情况下,进行 AOF 文件重写将占用大量资源,并导致服务器被短暂地阻塞。

RDB-AOF 混合持久化

由于 RDB 持久化和 AOF 持久化都有各自的优缺点,因此在很长一段时间里,如何选择合适的持久化方式成了很多 Redis 用户面临的一个难题。为了解决这个问题,Redis 从 4.0 版本开始引入 RDB-AOF 混合持久化模式,这种模式是基于 AOF 持久化模式构建而来的,如果用户打开了服务器的 AOF 持久化功能,并且将

aof-use-rdb-preamble <value>

设置为 yes,那么 Redis 服务器在执行 AOF 重写操作时,就会像执行BGSAVE命令那样,根据数据库当前的状态生成出相应的 RDB 数据,并将这些数据写入新建的 AOF 文件中,至于那些在 AOF 重写(BGREWRITEAOF)开始之后执行的 Redis 命令,则会继续以协议文本的方式追加到新 AOF 文件的末尾,即已有的 RDB 数据的后面。

所以,开启了 RDB-AOF 混合持久化后,服务器生成的 AOF 文件将由两个部分组成,其中位于 AOF 文件开头的是 RDB 格式的数据,而跟在 RDB 数据后面的则是 AOF 格式的数据。

结构
RDB 数据
AOF 数据

当一个支持 RDB-AOF 混合持久化模式的 Redis 服务器启动并载入 AOF 文件时,它会检查 AOF 文件的开头是否包含了 RDB 格式的内容:

  • 如果包含,那么服务器就会先载入开头的 RDB 数据,然后再载入之后的 AOF 数据。
  • 如果 AOF 文件只包含 AOF 数据,那么服务器将直接载入 AOF 数据。

所以为了避免全新的 RDB-AOF 混合持久化功能给传统的 AO F持久化功能使用者带来困惑,Redis 目前默认是没有打开 RDB-AOF 混合持久化功能的:aof-use-rdb-preamble no,如果要开启,需要用户手动设置 value 为 yes。

无持久化

即使用户没有显式地开启 RDB 持久化功能和 AOF 持久化功能,Redis 服务器也会默认使用以下配置进行 RDB 持久化:

save 6010000
save 300100
save 3600 1

如果用户想要彻底关闭这一默认的 RDB 持久化行为,让 Redis 服务器处于完全的无持久化状态,那么可以在服务器启动时向它提供以下配置选项:

save ""

这样一来,服务器将不会再进行默认的 RDB 持久化,从而使得服务器处于完全的无持久化状态中。处于这一状态的服务器在关机之后将丢失关机之前存储的所有数据,这种服务器可以用作单纯的内存缓存服务器。

优雅的关闭 Redis 服务器

如何优雅的关闭 Redis 服务器呢?那就是使用 SHUTDOWN命令,执行该命令,将执行如下动作:

  1. 停止处理客户端发送的命令请求。
  2. 如果服务器启用了 RDB 持久化功能,并且数据库距离最后一次成功创建 RDB 文件之后已经发生了改变,那么服务器将执行SAVE 命令,创建一个新的 RDB 文件。
  3. 如果服务器启用了 AOF 持久化功能或者 RDB-AOF 混合持久化功能,那么它将冲洗 AOF 文件,确保所有已执行的命令都被记录到了 AOF 文件中。
  4. 如果服务器既没有启用 RDB 持久化功能,也没有启用 AOF 持久化功能,那么服务器将略过这一步。
  5. 服务器进程退出。

所以只要服务器启用了持久化功能,那么使用SHUTDOWN命令来关闭服务器就不会造成任何数据丢失。SHUTDOWN命令提供的 save 选项或者 nosave 选项,显式地指示服务器在关闭之前是否需要执行持久化操作:

SHUTDOWN [save|nosave]

如果用户给定的是 save 选项,那么无论服务器是否启用了持久化功能,服务器都会在关闭之前执行一次持久化操作。如果用户给定的是 nosave 选项,那么服务器将不执行持久化操作,直接关闭服务器。在这种情况下,如果服务器在关闭之前曾经修改过数据库,那么它将丢失那些尚未保存的数据。

总结

总的来说,在数据持久化这个问题上,Redis 4.0 及之后版本的使用者都应该优先使用 RDB-AOF 混合持久化;对于 Redis 4.0 之前版本的使用者,因为 RDB 持久化更接近传统意义上的数据备份功能,而 AOF 持久化则更接近于传统意义上的数据持久化功能,所以如果用户不知道自己具体应该使用哪种持久化功能,那么可以优先选用 AOF 持久化作为数据持久化手段,并将 RDB 持久化用作辅助的数据备份手段。