【Redis】Redis 持久化 -- RDB && AOF

2024-08-26 15:05:20 浏览数 (2)

1 持久化介绍

我们知道,Redis 是基于 key-value 的 NoSQL 数据库,它的最大特点是速度非常快,几乎是 MySQL 的十万倍;而支撑 Redis 速度快特性的最主要原因是 Redis 中的数据都存储在内存中。

这里就引出了一个问题 – 内存中的数据是掉电易失的,且一旦进程退出内存中的对应数据也会被释放,那么我们要如何保证 Redis 数据库中的数据安全?

答案很简单,将内存中的数据持久化一份到硬盘中就好了,这样当我们读取数据时直接从内存中读取,当我们写入数据时同时写入内存与硬盘。但将数据写入到硬盘的速度是远慢于写入内存的,这会导致 Redis 的速度变慢,为了就可能降低数据写入硬盘对 Redis 性能带来的影响,Redis 提出不同的持久化策略 – RDB 与 AOF。

2 RDB

2.1 RDB 介绍

RDB 全称是 Redis Database,它的思想是定期将内存中的数据生成快照保存到硬盘中,即每隔一段时间将内存中的 key-value 键值对写入到特定的磁盘文件中,当 Redis 服务重启时,使用该文件中的数据来恢复内存中的数据。

2.2 触发方式

RDB 定期将内存数据快照保存到硬盘中,这里的定期具体表现为两种方式 – 自动触发和手动触发。

手动触发是指程序员通过 Redis 客户端执行特定的命令来触发快照生成,包括:

  • save:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,此命令对于内存比较大的实例可能造成长时间阻塞,因此基本不采用。
  • bgsave (background save):Redis 服务器通过 fork 创建子进程 的方式来完成 RDB,久化过程由子进程负责,完成后自动结束,一般不会阻塞服务器。

自动触发是指在配置文件 redis.conf 中进行相关项的配置,让 Redis 服务器达到某种条件时 (间隔多长时间以及发生了多少次修改) 自动触发 bgsave 命令,用户无感知。具体如下:

代码语言:javascript复制
################################ SNAPSHOTTING  ################################
#
# 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

注意配置项的描述 – 只有当经过的秒数以及最少修改次数都达标时,Redis 才会自动触发 bgsave。

2.3 流程介绍

下面我们来介绍一下 bgsave 命令的运行流程:

imgimg

如上图所示:

  1. 执行 bgsave 命令,Redis 父进程判断当前进程是否存在其他正在执行的子进程,如 RDB/AOF 子进程,如果存在则直接返回。
  2. 父进程执行 fork 创建子进程,此过程会发送阻塞,fork 得到的子进程会继承父进程的 task_struct 以及 mm_struct 等内存数据结构,使得子进程能够访问所有父进程的内存数据。
  3. 父进程 fork 完成后,bgsave 命令返回 "Background saving started" 信息并不再阻塞父进程,可以继续响应其他命令。
  4. 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成写入后对磁盘中已有的 RDB 文件进行原子替换 (文件的重命名操作是原子的)。
  5. 子进程发送信号给父进程表示 RDB 完成,父进程更新统计信息。

2.4 RDB 文件

关于 RDB 文件,有一些需要注意的地方:

  • RDB 文件默认保存路径为 /var/lib/redis/,可以通过配置文件中的 dir 选项进行配置; 默认文件名为 dump.rdb,可以通过配置文件中的 dbfilename 选型进行配置。
  • RDB 是二进制格式的文件,并且 Redis 会将内存中的数据以压缩的形式保存到此二进制文件中;这虽然消耗了一定的 CPU 资源,但能大幅度降低文件体积;同时二进制格式数据加载到内存后可以直接使用,因此 Redis 重启加载时, RDB 的速度要远快于 AOF。
  • RDB 文件中的数据可能由于人为修改或网络传输等原因导致格式被破坏,这会造成 Redis 无法重新启动,此时我们可以使用 Redis 提供的 redis-check-dump 工具,检测 RDB 文件并获取对应的错误报告。
  • Redis 服务器在正常退出时,比如执行 shutdown 命令,会自动执行 bgsave 生成 RDB 文件,便于下次重启时恢复内存数据;但如果 Redis 服务器异常退出,比如 kill -9 或者 掉电等,就会来不及生成 RDB 文件,导致部分内存数据丢失。

2.5 RDB 优缺点

优点:

  • RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照,非常适用于备份,全量复制等场景。比如每 6 小时执行 bgsave 备份,并把 RDB 文件复制到远程机器或者文件系统中(如 hdfs)用于灾备。
  • 由于 RDB 的二进制格式,Redis 加载 RDB 恢复数据的速度要远远快于 AOF 的方式 (AOF 采用文本格式存储)。

缺点:

  • RDB 方式数据没办法做到实时持久化 / 秒级持久化。因为 bgsave 每次运行都要执行 fork 创建子进程,然后由子进程进行持久化,这一过程中可能有新数据到来。
  • RDB 每次持久化都要全量复制内存中的数据,属于重量级操作,成本较高。
  • RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有风险。

3 AOF

3.1 AOF 介绍

我们上面学习了 RDB 持久化方式,RDB 最大的缺点在于无法做到实时持久化,两次生成快照之间的实时数据可能随着服务器异常退出而丢失。为了解决这个问题,Redis 提出了 AOF (Append Only File) 持久化。

AOF 以独立日志的方式来记录每次写命令,即将用户的每个修改操作都记录到文件中;Redis 重启时再从文件中读取这些命令然后重新执行,达到恢复数据的效果。AOF 解决了数据持久化的实时性,目前已经成为 Redis 持久化的主流方式。

AOF 的使用方式如下:

  • Redis 默认使用 RDB 方式进行持久化,要启用 AOF,需要修改配置文件中的 appendonly 选项为 yes
  • AOF 默认文件名为 appendonly.aof,可以通过 appendfilename 选项进行配置。
  • AOF 文件默认存储路径为 /var/lib/redis,与 RDB 一样,可以通过 dir 选项进行配置。
代码语言:javascript复制
############################## APPEND ONLY MODE ###############################

# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).                                                                                                 
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly yes

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

需要注意的是,当开启 AOF 后,RDB 就失效了,即服务器退出时不再自动生成 RDB 文件,服务器重启时也不使用 RDB,而是 使用 AOF 文件进行数据恢复。

既然 AOF 能做到实时持久化,那是不是意味着内存中的数据一定不会丢失?

不是,AOF 下也可能存在数据丢失:

  • Redis 是一个单线程程序,且最主要的特性是速度快,为了维持这个特性,Redis 必定不可能将所有修改真正的实时写入到硬盘中。
  • 实际上,AOF 机制并非是直接让一个工作线程把数据写入硬盘,而是先写到一个内存中的缓冲区,积累一波之后,再统一写入硬盘。这样能够大大降低硬盘的读写次数,从而减少对 Redis 效率的影响。
  • 那么这里就产生一个问题 – 将修改操作暂时写入到内存缓冲区中,本质上数据还是在内存中,此时如果 Redis 进程异常退出,缓冲区中的数据还是会丢失。

AOF 的工作流程如下:

  1. 所有的写入命令会追加到 aof_buf(缓冲区)中。
  2. AOF 缓冲区根据对应的策略向硬盘做同步操作。
  3. 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
  4. 当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复。
imgimg

3.2 缓冲区刷新策略

我们上面提到 AOF 实际上是先将数据写入内存缓冲区中,那么此时缓冲区的刷新频率就直接与数据可靠性挂钩 – 缓冲区刷新频率越高,数据可靠性越高,但相对的服务器性能就越低;缓冲区刷新频率越低,数据可靠性越低,但相对的服务器性能就越高。但无论刷新频率多高,只要不是一有修改就立即写入硬盘,就存在数据丢失的风险

因此,AOF 可以看作是效率与数据安全的一种折中方案,同时,为了满足不同应用场景下对效率以及数据安全的不同要求,Redis 支持用户对缓冲区刷新策略进行配置。可选的配置方案如下:

-可配置值

-说明

always

命令写入 aof_buf 后调用立即 fsync 同步,完成后返回

everysecs

命令写入 aof_buf 后只执行 write 操作,不进行 fsync,每秒由同步线程进行 fsync。

no

命令写入 aof_buf 后只执行 write 操作,由 OS 控制 fsync。

关于 write 与 fsync:

  • write 操作会触发延迟写(delayed write)机制。Linux 在内核提供页缓冲区用来提供硬盘 IO 性能。write 操作在写入系统缓冲区后立即返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
  • Fsync 针对单个文件操作,做强制硬盘同步,fsync 将阻塞直到数据写入到硬盘。

Redis 默认使用 everysecs 作为缓冲区刷新策略,可以通过配置文件的 appendfsync 选项进行配置。

代码语言:javascript复制
# 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

3.3 AOF 重写机制

3.3.1 重写机制介绍

由于 AOF 是不断向文件末尾追加内容的,因此随着命令不断写入 AOF,文件会逐渐变大,为了解决这个问题,Redis 引入 AOF 重写机制来压缩文件体积。较小的 AOF 文件一方面降低了硬盘空间占用,另一方面可以提升启动 Redis 时数据恢复的速度。

重写后的 AOF 为什么可以变小?有如下原因:

  • 进程内已超时的数据不再写入文件。
  • 旧的 AOF 中的无效命令,例如 del、hdel、srem 等重写后将会删除,只需要保留数据的最终版本。
  • 多条写操作合并为一条,例如 lpush list a、lpush list b、lpush list c 可以合并为 lpush list a b c。
  • 注:其实上面合并整理的最终数据就是内存中现有的数据。

因此,AOF 文件重写其实是把 Redis 进程内的数据转化为写命令同步到新的 AOF 文件

3.3.2 混合持久化
  • AOF 本来是按照文本的方式来写入文件的,但是文本的方式写文件,后续加载的成本是比较高的。
  • 因此,Redis 引入了混合持久化的方式,结合了 RDB 和 AOF 的特点。
  • 即按照 AOF 的方式,每一个请求/操作,都记录入文件。在触发 AOF 重写之后,就会把当前内存的状态按照 RDB 的二进制格式写入到新的 AOF 文件中。后续再进行的操作,仍然是按照 AOF 文本的方式追加到文件后面。

因此,我们也可以认为重写是保存当前内存数据的快照

Redis 中,混合持久化默认开启,我们可以通过配置文件中的 aof-use-rdb-preamble 配置项进行关闭。

代码语言:javascript复制
# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
#   [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes 
imgimg
3.3.3 重写触发方式

AOF 重写过程可以手动触发和自动触发:

  • 手动触发:调用 bgrewriteaof 命令。
  • 自动触发:根据配置文件中的 auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage 配置项确定自动触发时机。
    • auto-aof-rewrite-min-size:表示触发重写时 AOF 的最小文件大小,默认为 64MB。
    • auto-aof-rewrite-percentage:代表当前 AOF 占用大小相比较上次重写时增加的比例。
代码语言:javascript复制
# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
3.3.4 AOF 重写流程

AOF 的重写流程如下图所示:

imgimg
  1. 执行 AOF 重写请求:如果当前进程正在执行 AOF 重写,请求不执行;如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
  2. 父进程执行 fork 创建子进程,此时子进程保存了父进程当前时刻的内存状态。
  3. 父进程继续继续响应其他命令。 3.1 父进程将其他命令的修改操作写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证旧 AOF 文件机制正确。 3.2 子进程只有 fork 之前的所有内存信息,父进程中需要将 fork 之后这段时间的修改操作写入 AOF 重写缓冲区中。
  4. 子进程根据 fork 时刻的父进程的内存状态创建内存快照,生成新的 AOF 文件,文件内容为二进制格式 (混合持久化)。
  5. 子进程完成重写。 5.1 新 AOF 写入完毕后,子进程发送信号给父进程。 5.2 父进程把 AOF 重写缓冲区内临时保存的命令追加到新 AOF 文件中 (文本格式)。 5.3 用新 AOF 文件替换旧 AOF 文件。

上面有两个比较重要的细节:

  1. 由于子进程只有 fork 时的内存信息,同时子进程生成新 AOF 文件过程中可能有新的修改命令被执行,因此父进程需要维护一个重写缓冲区 aof_rewrite_buf,用来将这段时间内的修改操作追加到新 AOF 文件中。
  2. 重写过程以及文件替换过程可能因为某些因素失败,因此子进程在重写时父进程仍然需要向旧 AOF 文件中写入。

3.4 AOF 优缺点

AOF 持久化的优点在于内存数据更加安全,不易丢失。缺点也很明显,一是频繁的写入硬盘导致效率降低;二是以文本格式存储 AOF 修改操作,服务器重启时恢复数据需要重新执行 AOF 文件中的命令,不过混合持久化对这一点进行了优化。

4 启动时数据恢复

redis-server 启动时,会根据 RDB 和 AOF 文件的内容进行数据恢复。恢复过程如下:

image-20240825145100541image-20240825145100541

可以看到,AOF 是要优先于 RDB 的,只有当 AOF 未开启时,redis-server 才会使用 RDB 文件进行数据恢复。


0 人点赞