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 命令,用户无感知。具体如下:
################################ 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
命令的运行流程:
如上图所示:
- 执行 bgsave 命令,Redis 父进程判断当前进程是否存在其他正在执行的子进程,如 RDB/AOF 子进程,如果存在则直接返回。
- 父进程执行 fork 创建子进程,此过程会发送阻塞,fork 得到的子进程会继承父进程的
task_struct
以及mm_struct
等内存数据结构,使得子进程能够访问所有父进程的内存数据。 - 父进程 fork 完成后,bgsave 命令返回
"Background saving started"
信息并不再阻塞父进程,可以继续响应其他命令。 - 子进程创建 RDB 文件,根据父进程内存生成临时快照文件,完成写入后对磁盘中已有的 RDB 文件进行原子替换 (文件的重命名操作是原子的)。
- 子进程发送信号给父进程表示 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
选项进行配置。
############################## 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 的工作流程如下:
- 所有的写入命令会追加到 aof_buf(缓冲区)中。
- AOF 缓冲区根据对应的策略向硬盘做同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复。
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
选项进行配置。
# 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
配置项进行关闭。
# 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
3.3.3 重写触发方式
AOF 重写过程可以手动触发和自动触发:
- 手动触发:调用
bgrewriteaof
命令。 - 自动触发:根据配置文件中的
auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
配置项确定自动触发时机。auto-aof-rewrite-min-size
:表示触发重写时 AOF 的最小文件大小,默认为 64MB。auto-aof-rewrite-percentage
:代表当前 AOF 占用大小相比较上次重写时增加的比例。
# 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 的重写流程如下图所示:
- 执行 AOF 重写请求:如果当前进程正在执行 AOF 重写,请求不执行;如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
- 父进程执行 fork 创建子进程,此时子进程保存了父进程当前时刻的内存状态。
- 父进程继续继续响应其他命令。 3.1 父进程将其他命令的修改操作写入 AOF 缓冲区并根据 appendfsync 策略同步到硬盘,保证旧 AOF 文件机制正确。 3.2 子进程只有 fork 之前的所有内存信息,父进程中需要将 fork 之后这段时间的修改操作写入 AOF 重写缓冲区中。
- 子进程根据 fork 时刻的父进程的内存状态创建内存快照,生成新的 AOF 文件,文件内容为二进制格式 (混合持久化)。
- 子进程完成重写。 5.1 新 AOF 写入完毕后,子进程发送信号给父进程。 5.2 父进程把 AOF 重写缓冲区内临时保存的命令追加到新 AOF 文件中 (文本格式)。 5.3 用新 AOF 文件替换旧 AOF 文件。
上面有两个比较重要的细节:
- 由于子进程只有 fork 时的内存信息,同时子进程生成新 AOF 文件过程中可能有新的修改命令被执行,因此父进程需要维护一个重写缓冲区
aof_rewrite_buf
,用来将这段时间内的修改操作追加到新 AOF 文件中。 - 重写过程以及文件替换过程可能因为某些因素失败,因此子进程在重写时父进程仍然需要向旧 AOF 文件中写入。
3.4 AOF 优缺点
AOF 持久化的优点在于内存数据更加安全,不易丢失。缺点也很明显,一是频繁的写入硬盘导致效率降低;二是以文本格式存储 AOF 修改操作,服务器重启时恢复数据需要重新执行 AOF 文件中的命令,不过混合持久化对这一点进行了优化。
4 启动时数据恢复
redis-server 启动时,会根据 RDB 和 AOF 文件的内容进行数据恢复。恢复过程如下:
可以看到,AOF 是要优先于 RDB 的,只有当 AOF 未开启时,redis-server 才会使用 RDB 文件进行数据恢复。