Redis 持久化原理分析和使用建议

数据库
本文主要介绍了 Redis 提供的三大持久化机制,即 AOF 日志、RDB 快照以及混合持久化机制。

一、Redis 为什么需要进行持久化

Redis 为了保证性能,会将所有数据存放在内存中,这极大的提高了 Redis 的响应速度,但是这也引入了一个十分严重的问题:一旦服务器宕机,内存中的数据将全部丢失。这对于一款数据库产品来说是不能接受的。要避免数据丢失,最好将内存数据持久化到磁盘等永久存储介质上。服务重启时,先加载磁盘文件内的数据到内存,完成数据恢复。目前,Redis 主要提供了三大持久化机制,即 AOF(Append Only File)日志、RDB 快照、AOF 与 RDB 相结合的混合持久化。

二、AOF

2.1  AOF 概述

AOF 全称为 Append Only File,即实时将每一条写命令记录到 AOF 文件中,当 Redis 服务重启时会顺序执行一遍 AOF 文件中的所有命令,以达到数据恢复的目的。我们常见的日志记录方式主要为两种,一种为写前日志(Write Ahead Log, WAL),以 Mysql 数据库为例,Mysql 在将数据写入磁盘前,会先把修改的数据记到 redo 和 bin log 日志文件中,以便故障时进行恢复。

而另一种则是 Redis AOF 日志所采用的写后日志,“写后”的意思是 Redis 会先执行所接收到的写命令,把数据写入内存,然后才将命令写入 AOF 日志文件。Redis 在将命令记录到 AOF 日志中的时候,并不会去检验命令语法的正确性。因此先执行命令,利用命令执行这一环节对这些命令进行语法校验,这样既避免了写日志时额外的检查开销,也不会将存在语法错误的命令记录到 AOF 日志中,保证了 Redis 在使用 AOF 日志恢复数据时的正确性。


图片

2.2 AOF 开启方式

由于 AOF 功能开启后对 Redis 性能会产生一定影响,因此 AOF 功能默认是关闭的。我们可以通过修改 redis.conf 配置文件中的配置参数来开启 AOF 功能,还可以通过“config set”命令在线动态的开启和关闭 AOF 功能。

图片

2.3 AOF 配置项

我们可以通过 redis.conf 配置文件中与 AOF 相关的配置项来自定义相应的 AOF 功能,例如: 

  • 可以通过appendfilename、appenddirname 配置项来指定 AOF 文件名以及文件存放目录。
  • 可以通过 appendfsync 配置项来指定 AOF 刷盘策略,其中不同的配置项值在数据安全性以及服务性能方面的表现都不相同。
  • 配置项 no-appendfsync-on-rewrite 表示是否在 AOF 重写或写 RDB 期间,进行 AOF 刷盘。在 AOF 重写和写 RDB 文件期间会产生大量的磁盘 IO 读写操作,如果配置为 no,则表示在此期间可以进行刷盘操作,此时如果需要同步的数据量非常大可能会阻塞 Redis 主线程,降低 Redis 服务性能。因此在生产环境中,如果存在大量并发的写操作则可将该值设置为 yes;如果为大量并发的读操作则可以设置为 no。
  • 可以通过 auto-aof-rewrite-percentage、auto-aof-rewrite-min-size 配置项来指定触发 AOF 重写的条件,以防止 AOF 文件过度膨胀,占用大量存储空间。
  • 在进行 AOF 持久化期间,可能由于 Redis 服务突然宕机等原因导致写入 AOF 文件的最后一条命令不完整。在 Redis 服务重启并加载 AOF 文件时,配置项 aof-load-truncated 的值决定了 Redis 服务能否重启成功。若值为 yes,则删除不完整的命令,Redis 服务正常启动;若值为 no,则 Redis 服务无法启动。
  • 可以通过 aof-use-rdb-preamble 配置项来指定是否开启混合持久化功能。

2.4 AOF 文件内容

通过上述方式开启 AOF 功能后,我们以一条 “set testkey testvalue” 命令来看一下命令是以怎样的格式存储在 AOF 文件中的。如图所示其中,“*3” 表示当前命令有三个部分,每部分都是由 “$+数字” 开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3” 表示这部分数据有 3 个字节,也就是 “set” 命令。

图片

2.5 写回策略

Redis 是先执行写操作命令,然后将该命令记录到 AOF 缓冲区中,最后再将缓冲区中的数据写入磁盘。这么做避免了额外的检查开销,不会阻塞当前写操作命令的执行。AOF 配置项 appendfsync 的参数值,为 AOF 日志写回磁盘的时机提供了三个选择。

  • Always,同步写回:每个写命令执行完,立马将日志写回磁盘;“同步写回”可以做到基本不丢数据,但是在每一个写命令执行后都会有一个对磁盘的写操作,这不可避免地会影响主线程性能。
  • Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;“每秒写回”采用一秒写回一次的频率,避免了“同步写回”的性能开销,减少了对 Redis 性能的影响,但是如果发生宕机,将丢失一秒内的所有写操作。
  • No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。由于 Redis 已不再掌握落盘的时机,因此一旦宕机没有写入 AOF 文件的数据就丢失了。

写回策略对比:



配置项





写回策略





优点





缺点





Always





同步写回





可靠性高,基本不丢失数据





性能影响较大,每一个写命令后都需要进行落盘操作





Everysec





每秒写回





性能适中





可能丢失1秒内的数据





No





操作系统控制写回





性能好





可靠性差,可能丢失大量数据



2.6 AOF 重写

在上面我们向 AOF 文件中记录了一条 set 命令,如果此时我们对该 key 的值进行了更新,那么 AOF 文件中记录的日志是否也会随之更新呢?通过 AOF 文件我们可以看到,原先写入AOF 文件的 set 命令并没有发生改变,而是在 AOF 文件末尾追加了一条 set testkey value 命令。

图片

由于 AOF 日志文件中记录的是自 Redis 启动后每一条写操作,因此随着写操作的不断增加,AOF 文件将越来越膨胀,为了防止 AOF 文件不断地膨胀,Redis 为我们提供了一种 AOF 文件的重写机制。AOF 文件在重写时,Redis 会根据数据库的现状创建一个新的 AOF 文件,即读取数据库中的所有键值对,然后用一条命令将每一个键值对记录到新的 AOF 文件中。比如说,当读取了键值对 “testkey”: “value” 之后,重写机制会记录 set testkey value 这条命令。

这样,当需要恢复时,可以重新执行该命令,实现 “testkey”: “value” 的写入,而并不关心中间对这个 key 的其他写操作。以下图为例,当我们对一个列表先后做了 6 次修改操作后,列表的最后状态是 [“D”, “E”, “F”],此时,只用 LPUSH mylist “D”, “E”, "F" 这一条命令就能实现该数据的恢复,这就节省了五条命令的空间。这样对于被频繁修改过的键值对来说,重写能节省大量的空间。

图片

注:图片来源于 https://time.geekbang

虽然 AOF 重写后,日志文件会缩小,但是,要把整个数据库最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。为了避免阻塞主线程,导致数据库性能下降,重写过程是由 bgrewriteaof 子进程来完成。每次执行重写时,主线程 fork 出 bgrewriteaof 子进程。由子进程逐一把内存中的数据写成操作,记入重写日志。由于主线程未阻塞,仍然可以处理新来的操作。

此时,如果有写操作,Redis 会把这个操作写到 AOF 日志缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。并且这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到内存数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,就可以用新的 AOF 文件替代旧文件了。

图片

注:图片来源于 https://time.geekbang

触发 AOF 后台重写的条件

  • AOF 重写可以由用户通过调用 `BGREWRITEAOF` 手动触发。
  • 每次当 `serverCron`(服务器周期性操作函数)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的 AOF 重写操作:

(1)没有 BGSAVE 命令(RDB 持久化)/ AOF 持久化在执行;
(2)没有 BGREWRITEAOF 在进行;

(3)当前 AOF 文件大小要大于

 `server.aof_rewrite_min_size`(默认为1MB),或者在 `redis.conf` 配置了 `auto-aof-rewrite-min-size` 大小;

(4)当前 AOF 文件大小和最后一次重写后的大小之间的比率大于或者等于指定的增长百分比(在配置文件设置了 `auto-aof-rewrite-percentage` 参数,不设置默认为100%)

2.7 AOF 持久化详细流程

  • Redis 在接收到一条客户端发送过来的写命令后,会先执行该写命令,待命令执行成功后将该命令按照 Redis 通信协议格式写入 AOF 缓冲区。
  • 根据设置的 AOF 写回策略,当满足写回条件时,将 AOF 缓冲区中的数据写入磁盘 AOF 文件中。
  • 当 AOF 文件大小达到重写条件后,主进程 fork 出 bgrewriteaof 子进程,由子进程将内存中的数据重写为写命令写入临时文件。
  • 若重写期间,主线程执行了新的写操作,主线程在将该写操作记录到 AOF 缓冲区的同时会将该写操作写入重写缓存区。
  • 待子进程完成内存中数据的记录后,会将重写缓存区记录的数据一并写入临时文件。
  • 最后利用临时文件替换原来的 AOF 文件。

图片

2.8 AOF 日志优缺点

优点:

(1)AOF 可以更好的保护数据不丢失,一般 AOF 会每隔1秒,通过一个后台线程执行 fsync 操作,最多丢失 1 秒钟的数据。
(2)Redis 提供了 AOF 文件的重写机制,因此 AOF 日志文件不会膨胀的很大,并且在重写期间也不会影响客户端的读写。

(3)AOF 文件中保存的是执行的指令,所以这个特性非常适合做灾难性的误操作紧急恢复。

缺点:

(1)由于 AOF 文件中记录的是写操作的命令,因此对于同一份数据来说,AOF 的日志文件通常要比 RDB 的数据快照文件要大。
(2)AOF 开启之后,Redis 服务支持的写 QPS 会比 RDB 支持的写 QPS 低。

三、RDB

3.1 RDB 概述

RDB 持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录。这样一来,即使宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。这个快照文件就称为 RDB 文件,其中,RDB 就是 Redis DataBase 的缩写。因为 RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,我们可以直接把 RDB 文件读入内存,很快地完成数据恢复。

3.2 RDB 触发方式

3.2.1 save 触发方式

save 命令执行时将阻塞 Redis 服务器,在此期间 Redis 不能执行其他客户端的命令,直到 RDB 过程完成为止。执行完成后,用新的 RDB 文件替代掉旧的 RDB 文件。由于 save 命令会阻塞 Redis 服务器,极大的影响了 Redis 的性能,因此不推荐 save 命令触发方式。

图片


3.2.2 bgsave 触发方式

执行该命令时,Redis 会在后台异步将数据记录到 RDB 文件中,同时 Redis 还可以响应客户端请求。bgsave 命令执行时 Redis 主进程将执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后用新的 RDB 文件替换掉旧的 RDB 文件。阻塞只发生在 fork 阶段,一般时间很短。基本上 Redis 内部所有的 RDB 操作都是采用 bgsave 命令。

图片


3.2.3 自动触发

自动触发是由我们的配置文件来完成的。在 redis.conf 配置文件中,里面有如下配置:

save:这里是用来配置触发 Redis 的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如 “save m n”。表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave。因此自动触发的整体流程和 bgsave 命令触发是一致的。

RDB 触发方式对比



命令





save





bgsave





IO类型





同步





异步





阻塞?









是(阻塞发生在fork 阶段)





优点





不会消耗额外内存





不阻塞 Redis 服务器





缺点





阻塞 Redis 服务器





需要 fork,消耗内存



3.3 RDB 配置项

在 redis.conf 配置文件中除了用于开启 RDB 功能的 save 配置项外,还有其他用于自定义 RDB 功能的配置项,例如:

  • 开启 RDB 功能后,可以将配置项 stop-writes-on-bgsave-error 的值设置为 yes,在 bgsave 命令执行失败后阻止后续的写命令执行。通过这种方式可以让用户意识到数据并没有被成功的持久化到磁盘,从而避免数据丢失问题。
  • 可以通过配置项 rdbcompression 来启用 LZF 对 RDB 文件进行压缩,虽然压缩后 RDB 文件占用的存储空间变小,但是压缩过程会消耗系统资源,降低 Redis 服务性能。
  • 从 RDB 5 开始,RDB 文件将 CRC64 校验和写入文件末尾,以保证 RDB 文件的正确性,但这在保存和加载 RDB 文件时,对性能会产生影响。因此可以通过将配置项 rdbchecksum 的值设置为 no,禁用校验和。
  • 可以通过配置项 dbfilename 和 dir 来指定 RDB 文件名和生成目录。 

3.4 写时复制技术

当由 bgsave 方式触发 RDB 持久化时,Redis 在写 RDB 期间是可以正常处理客户端发来的写命令的。因此为了节省在 RDB 持久化过程中内存的使用,Redis 会借助操作系统提供的写时复制技术(Copy-On-Write, COW)。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。

此时,如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件。这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

图片

注:图片来源于 https://time.geekbang

3.5 RDB 文件格式

 RDB 文件由以下五个部分组成;

  1. SOF 为一个长度为 5 的字符串常量 REDIS;用于标识 RDB 文件的开始,在加载 RDB 文件时可以快速识别该文件是否为 RDB 文件。
  2. rdb_version 占 4 个字节,表示 RDB 文件的版本号。
  3. database 是 RDB 文件中的重要部分,包含了多个非空的数据库。每个 database 又由3个部分组成:SODB 占一个字节表明数据库的开始,db_number 表示数据库的编号,key-value-pairs 表示当前数据库中的键值对。
  4. EOF 占一个字节,用于标识数据的结束,校验和的开始。
  5. check_sum 校验和,用于校验 RDB 文件数据是否出现损坏。

图片

3.6 RDB 快照优缺点

优点
1)RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,非常适合做冷备。
(2)由于 Redis 是通过子进程执行磁盘 IO 操作来记录 RDB 文件的,因此 RDB 机制对 Redis 的性能影响非常小,阻塞只发生在 fork 阶段,可以保持 Redis 的高性能。
(3)相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加的快速。

缺点
(1)由于 RDB 文件记录的是某个时间段内的数据集,因此当服务器发生宕机时,该时间段后的数据将丢失。
(2)主进程每次在 fork 子进程来记录数据时,如果数据文件特别大,可能会导致主进程阻塞数毫秒,或者甚至数秒。

四、混合持久化方式

由于 RDB 文件记录的是某个时间段内的数据集,因此两次 RDB 期间的数据依然存在丢失的风险,但是制作 RDB 文件的频率太高又会对 Redis 性能带来影响。因此为了避免两次 RDB 期间数据的丢失,并且降低对性能所带来的影响, Redis 4.0 提出了一个混合使用 AOF 日志和内存快照的方法。可以通过 redis.conf 配置文件中的配置项 aof-use-rdb-preamble 来开启混合持久化功能。

利用 AOF 日志记录两次快照间的操作,因此, AOF 文件也不会太大,也可以避免重写开销。如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

图片

注:图片来源于 https://time.geekbang

五、持久化方案选择

 Redis 官方推荐使用混合持久化方案,但我们也需要根据具体的业务需求和场景并结合三种持久化方案的优缺点选择合适的持久化方案。

  1. 如果对数据安全性有较高的要求,就可以选择 AOF 或 混合持久化方案,其中 AOF 持久化方案可通过选择不同的写回策略来达到不同程度的数据安全性保证。
  2. 如果希望数据快速恢复,减少故障恢复时间,可以选择 RDB 或混合持久化方案。
  3. 如果对持久化文件大小有要求,可以选择 RDB 持久化方案,并可以启用 LZF 对 RDB 文件进行压缩。
  4. 如果对 Redis 服务性能有较高的要求,可以选择 RDB 或混合持久化方案。
  5. 若 Redis 仅用于缓存,可不开启持久化功能以追求最大的服务性能。 

六、参考资料

责任编辑:庞桂玉 来源: vivo互联网技术
相关推荐

2020-02-18 16:14:33

RedisRDBAOF

2021-10-04 21:11:18

Redis混合持久化

2017-10-20 15:25:17

DockerOpenStack Cvolume

2019-11-12 14:15:07

Redis内存持久化

2023-05-11 09:12:35

RedisRDB日志

2023-10-12 13:01:29

Redis数据库

2020-03-03 14:15:49

Redis持久化数据库

2019-05-17 08:55:49

RedisRDBAOF

2024-03-26 00:03:08

Redis数据RDB

2009-09-29 16:46:01

创建Hibernate

2024-12-20 12:15:06

RedisRDB持久化

2021-03-10 00:02:01

Redis

2019-12-27 13:50:04

JavaAPI代码

2020-12-11 11:40:37

RDBAOFRedis

2023-09-12 10:49:44

Redis数据库

2021-12-15 19:22:38

原理View动画

2020-01-06 14:54:31

RDBAOFRedis

2021-07-16 11:40:58

鸿蒙HarmonyOS应用

2021-07-18 07:59:42

RedisRDBAOF

2021-12-12 10:29:41

AOFRedisAOF日志
点赞
收藏

51CTO技术栈公众号