Redis 是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将 Redis 中的数据以某种形式(数据或命令)从内存保存到硬盘。当下次 Redis 重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。Redis 的持久化机制有两种:
- RDB(Redis Data Base) 内存快照
- AOF(Append Only File) 增量日志
持久化的执行
RDB ( Redis Data Base) 指的是在指定的时间间隔内将内存中的数据集快照写入磁盘,RDB 是内存快照(内存数据的二进制序列化形式)的方式持久化,每次都是从 Redis 中生成一个快照进行数据的
全量备份。是 Redis 默认使用的持久化功能。
SAVE:阻塞服务器并创建 RDB 文件
通过在 redis-cli 客户端中执行 save 命令可立即进行一次持久化保存。save 命令在执行期间会阻塞 redis-server 进程,直至持久化过程完毕。而在 redis-server 进程阻塞期间,Redis 不能处理任何读写请求,无法对外提供服务。
127.0.0.1:6379> saveOKBGSAVE:以非阻塞方式创建 RDB 文件
通过在 redis-cli 客户端中执行 bgsave 命令可立即进行一次持久化保存。不同于 save 命令的是,正如该命令的名称一样,background save,后台运行 save。bgsave 命令会使服务器进程 redis-server 生成一个子进程,由该子进程负责完成保存过程。在子进程进行保存过程中,不会阻塞 redis-server 进程对客户端读写请求的处理。
127.0.0.1:6379> bgsaveBackground saving started需要注意的是由于执行 BGSAVE 命令需要创建子进程,所以父进程占用的内存数量越大,创建子进程这一操作耗费的时间也会越长,因此 Redis 服务器在执行 BGSAVE 命令时,仍然可能会由于创建子进程而被短暂地阻塞。
通过配置文件自动创建 RDB 文件
自动条件触发的本质仍是 bgsave 命令的执行。只不过是用户通过在配置文件中做相应的设置后,Redis 会根据设置信息自动调用 bgsave 命令执行。
# 除非另有指定,否则默认情况下 Redis 会保存数据库:# * 如果至少执行了 1 次修改操作,则在 3600 秒(1 小时)后保存# * 如果至少执行了 100 次修改操作,则在 300 秒(5 分钟)后保存# * 如果至少执行了 10000 次修改操作,则在 60 秒后保存## 你可以取消下面这行的注释来显式设置这些规则。# ------------------------------------------------------------------------# Unless specified otherwise, by default Redis will save the DB:# * After 3600 seconds (an hour) if at least 1 change was performed# * After 300 seconds (5 minutes) if at least 100 changes were performed# * After 60 seconds if at least 10000 changes were performed## You can set these explicitly by uncommenting the following line.# save 3600 1 300 100 60 10000持久化策略按照如下顺序进行:
- 如果服务器在 60 秒之内,对数据库进行了至少 10000 次修改,则进行持久化。
- 如果服务器在 300 秒之内,对数据库进行了至少 100 次修改,则进行持久化。
- 如果服务器在 3600 秒(一小时)之内,对数据库进行了至少 1 次修改,则进行持久化。
查看最近持久化时间
通过 lastsave 命令可以查看最近一次执行持久化的时间,其返回的是一个 Unix 时间戳。
127.0.0.1:6379> lastsave(integer) 1786795674RDB 优化配置
RDB 相关的配置在 redis.conf 文件的 SNAPSHOTTING 部分。
-
save
该配置用于设置快照的自动保存触发条件,即 save point,保存点。该触发条件是在指定时间段内发生了指定次数的写操作。如果不启用 RDB 持久化,只需设置 save 的参数为空串即可:save ""。 -
stop-write-on-bgsave-error
bash# 但是,如果你已经为 Redis 服务器和持久化机制配置了完善的监控,# 那么你可能希望禁用此功能,这样即使磁盘、权限等方面出现问题,# Redis 仍然可以像往常一样继续工作。# ------------------------------------------------------------------------# However if you have setup your proper monitoring of the Redis server# and persistence, you may want to disable this feature so that Redis will# continue to work as usual even if there are problems with disk,# permissions, and so forth. stop-writes-on-bgsave-error yes理解如果在没有设置持久化正确结果的监控的情况下,启用了 RDB 且最后一次持久化数据失败(磁盘损坏,权限等原因),Redis 就会停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到出故障了。当然,如果 bgsave 命令后来可以正常工作了,Redis 将自动允许再次写入。
-
rdbcompression
bash# 在转储 .rdb 数据库时,是否使用 LZF 压缩字符串对象?# 默认情况下启用压缩,因为这样几乎总是有收益的。# 如果希望节省负责执行保存操作的子进程的 CPU 资源,可以将其设置为 'no',# 但如果数据中的值或键具有较好的可压缩性,数据集可能会变得更大。# --------------------------------------------------------------------------# Compress string objects using LZF when dump .rdb databases?# By default compression is enabled as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes理解设置为 yes 时,进行持久化会启用 LZF 算法压缩来压缩字符串对象。虽然压缩 RDB 文件会消耗系统资源和 CPU,降低性能,但可大幅降低磁盘文件的大小,方便保存到磁盘,加速主从集群中从节点的数据同步。
-
rdbchecksum
bash# 从 RDB 版本 5 开始,文件末尾会保存一个 CRC64 校验和。# 这使得 RDB 文件格式具有更强的抗损坏能力,但在保存和加载 RDB 文件时# 会带来一定的性能开销(大约 10%),因此可以禁用该功能以获得最大性能。## 禁用校验和后创建的 RDB 文件,其校验和为 0,# 加载代码会根据这一值跳过校验。# --------------------------------------------------------------------------------# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.# This makes the format more resistant to corruption but there is a performance# hit to pay (around 10%) when saving and loading RDB files, so you can disable it# for maximum performances.# # RDB files created with checksum disabled have a checksum of zero that will# tell the loading code to skip the check.rdbchecksum yes -
sanitize-dump-payload
bash# 在加载 RDB 或 RESTORE payload 时,启用或禁用对 ziplist、listpack 等数据结构的完整# 清理检查。这可以降低 Redis 在后续处理命令时发生断言失败或崩溃的可能性。# 可选项:# no - 从不执行完整的清理检查# yes - 始终执行完整的清理检查# clients - 仅对用户连接执行完整的清理检查。# 不包括:RDB 文件、从主节点连接接收到的 RESTORE 命令,# 以及具有 skip-sanitize-payload ACL 标志的客户端连接。# 默认值应该是 'clients',但由于目前它会影响通过 MIGRATE 进行的集群# 重新分片操作,因此暂时将默认值设置为 'no'。# ------------------------------------------------------------------------------# Enables or disables full sanitization checks for ziplist and listpack etc when# loading an RDB or RESTORE payload. This reduces the chances of a assertion or# crash later on while processing commands.# Options:# no - Never perform full sanitization# yes - Always perform full sanitization# clients - Perform full sanitization only for user connections.# Excludes: RDB files, RESTORE commands received from the master# connection, and client connections which have the# skip-sanitize-payload ACL flag.# The default should be 'clients' but since it currently affects cluster# resharding via MIGRATE, it is temporarily set to 'no' by default.## sanitize-dump-payload no -
dbfilename
bash# 用于保存数据库的文件名# ---------------------------------# The filename where to dump the DBdbfilename dump.rdb -
rdb-del-sync-files
bash# 删除在未启用持久化的实例中用于复制的 RDB 文件。# 默认情况下,此选项处于禁用状态,但在某些环境中,出于法规要求或其他安全方面的考虑,# 主节点为了向副本节点提供数据而持久化到磁盘上的 RDB 文件,# 或副本节点为了进行初始同步而存储在磁盘上的 RDB 文件,都应该尽快删除。# 请注意,此选项仅适用于同时禁用了 AOF 和 RDB 持久化的实例,# 否则该选项将被完全忽略。## 另一种(有时也是更好的)实现相同效果的方法是,# 在主节点和副本节点上都使用无磁盘复制(diskless replication)。# 不过,对于副本节点来说,无磁盘复制并不总是可用的选择。# ------------------------------------------------------------------------# Remove RDB files used by replication in instances without persistence# enabled. By default this option is disabled, however there are environments# where for regulations or other security concerns, RDB files persisted on# disk by masters in order to feed replicas, or stored on disk by replicas# in order to load them for the initial synchronization, should be deleted# ASAP. Note that this option ONLY WORKS in instances that have both AOF# and RDB persistence disabled, otherwise is completely ignored.## An alternative (and sometimes better) way to obtain the same effect is# to use diskless replication on both master and replicas instances. However# in the case of replicas, diskless is not always an option.rdb-del-sync-files no理解主从复制时,是否删除用于同步的从机上的 RDB 文件。默认是 no,不删除。不过需要注意,只有当从机的 RDB 和 AOF 持久化功能都未开启时才生效。
-
dir
bash# 工作目录。## 数据库文件将写入此目录中,文件名由上面的 'dbfilename' 配置指令指定。## Append Only File(AOF)文件也会在此目录中创建。## 请注意,这里必须指定一个目录,而不是文件名。# ------------------------------------------------------------------------# The working directory.## The DB will be written inside this directory, with the filename specified# above using the 'dbfilename' configuration directive.## The Append Only File will also be created inside this directory.## Note that you must specify a directory here, not a file name.dir /usr/local/redis/data
RDB 文件结构
rdb 文件整体上可分为三个区域:头信息区、数据区、尾信息区。
1. 头部信息区
-
魔数
以 ascii 码 “REDIS” 开头。用来验证当前文件的格式。类似 java 程序编译后的 class 文件,以 “CAFEBABE” 魔数开头,寓意 java 和 cafe 的千丝万缕的关系。
-
RDB version
表示 RDB 文件的版本。并且高版本 redis 可以 100% 向后兼容加载旧版 rdb 文件。版本信息使用 4 个字节表示。 如:
30 30 30 37转换为 ascii 码00 00 00 07,代表 rdb version 文件为第 7 版。 -
AUX metadata
可以存放多组 key-value,用来表示对应元信息。每一对 key-value,都以 0xFA 开头。key 和 value 均采用 rdb 字符串编码方法。
默认元信息列表:
- redis-ver:redis 版本信息。
- redis-bits:输出 rdb 文件机器的位数,64bit 或 32 bit。
- ctime:rdb 文件创建时间。
- used-mem:rdb 加载到内存中的内存使用量。
2. 数据区
-
SELECTDB
常量的长度为 1 字节。当读入程序遇到这个值的时候, 它知道接下来要读入的将是一个数据库号码。
-
db_number
保存着一个数据库号码, 根据号码的大小不同, 这个部分的长度可以是 1 字节、 2 字节或者 5 字 B 节。 当程序读入 db_number 部分之后, 服务器会调用 SELECT 命令, 根据读入的数据库号码进行数据库切换, 使得之后读入的键值对可以载入到正确的数据库中。
-
key_value_pairs
保存了数据库中的所有键值对数据。
不带过期时间的键值对:
-
TYPE 记录了 value 的类型, 长度为 1 字节, 值可以是以下常量的其中一个:
REDIS_RDB_TYPE_STRINGREDIS_RDB_TYPE_LISTREDIS_RDB_TYPE_SETREDIS_RDB_TYPE_ZSETREDIS_RDB_TYPE_HASHREDIS_RDB_TYPE_LIST_ZIPLISTREDIS_RDB_TYPE_SET_INTSETREDIS_RDB_TYPE_ZSET_ZIPLISTREDIS_RDB_TYPE_HASH_ZIPLIST
以上列出的每个 TYPE 常量都代表了一种对象类型或者底层编码, 当服务器读入 RDB 文件中的键值对数据时, 程序会根据 TYPE 的值来决定如何读入和解释 value 的数据。
-
key 总是一个字符串对象, 它的编码方式和 REDIS_RDB_TYPE_STRING 类型的 value 一样。 根据内容长度的不同, key 的长度也会有所不同。
-
value 会根据 TYPE 类型的不同, 以及保存内容长度的不同, 保存 value 的结构和长度也会有所不同。
带有过期时间的键值对:
- EXPIRETIME_MS 常量的长度为 1 字节, 它告知读入程序, 接下来要读入的将是一个以毫秒为单位的过期时间。
- ms 是一个 8 字节长的带符号整数, 记录着一个以毫秒为单位的 UNIX 时间戳, 这个时间戳就是键值对的过期时间。
- 带有过期时间的键值对中的 TYPE 、 key 、 value 三个部分的意义, 和前面介绍的不带过期时间的键值对的 TYPE 、 key 、 value 三个部分的意义完全相同。
-
3. 尾信息区
该区域最为简单。固定使用 9 byte。第一 byte 为 0xFF ,之后固定跟着 8 byte 用于 crc64 校验。该校验码采用 crc-64-jones 算法生成,用于校验 rdb 文件的合法性。
RDB 持久化过程
RDB 持久化方案进行备份时,Redis 会单独 fork 一个子进程来进行持久化,会将数据写入一个临时文件中,持久化完成后替换旧的 RDB 文件。在整个持久化过程中,主进程(为客户端提供服务的进程)不参与 IO 操作,这样能确保 Redis 服务的高性能,RDB 持久化机制适合对数据完整性要求不高但追求高效恢复的使用场景。下面展示 RDB 持久化流程:
关键执行步骤如下
-
Redis 父进程首先判断:当前是否在执行 save,或 bgsave/bgrewriteaof 的子进程,如果在执行则 bgsave 命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
-
父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的,Redis 不能执行来自客户端的任何命令。父进程 fork 后,bgsave 命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令。
-
子进程进程对内存数据生成快照文件。
-
父进程在此期间接收的新的写操作,使用 COW 机制写入。
-
子进程完成快照写入,替换旧 RDB 文件,随后子进程退出。
在生成 RDB 文件的步骤中,在同步到磁盘和持续写入这个过程是如何处理数据不一致的情况呢?生成快照 RDB 文件时是否会对业务产生影响?
Fork 子进程的作用
上面说到了 RDB 持久化过程中,主进程会 fork 一个子进程来负责 RDB 的备份,这里简单介绍一下 fork:
-
Linux 操作系统中的程序,fork 会产生一个和父进程完全相同的子进程。子进程与父进程所有的数据均一致,但是子进程是一个全新的进程,与原进程是父子进程关系。
-
出于效率考虑,Linux 操作系统中使用 COW(Copy On Write)写时复制机制,fork 子进程一般情况下与父进程共同使用一段物理内存,只有在进程空间中的内存发生修改时,内存空间才会复制一份出来。
在 Redis 中,RDB 持久化就是充分的利用了这项技术,Redis 在持久化时调用 glibc 函数 fork 一个子进程,全权负责持久化工作,这样父进程仍然能继续给客户端提供服务。fork 的子进程初始时与父进程(Redis 的主进程)共享同一块内存;当持久化过程中,客户端的请求对内存中的数据进行修改,此时就会通过 COW (Copy On Write) 机制对数据段页面进行分离,也就是复制一块内存出来给主进程去修改。
数据丢失情况分析
save 命令数据丢失
| 时间 | 事件 |
|---|---|
| T0 | 服务器开始运行 |
| T1 | 服务器执行 SET k1 v1 |
| T2 | 服务器执行 SET k2 v2 |
| T3 | 服务器执行 SAVE 命令,成功创建 RDB 文件 |
| T4 | 服务器执行 SET k3 v3 |
| T5 | 服务器执行 SET k4 v4 |
| T6 | 服务器执行 SAVE 命令,成功创建 RDB 文件 |
| T7 | 服务器执行 SET k5 v5 |
| T8 | 服务器执行 SET k6 v6 |
| T9 | 服务器停机 |
- 因为服务器最后一次成功执行 SAVE 命令是在 T6,所以服务器创建出的 RDB 文件将包含键 k1 至键 k4 在内的数据,服务器在重启时将使用这个 RDB 文件进行数据恢复
- 因为服务器在 T6 之后创建了键 k5 和键 k6,并在之后出现停机,所以当服务器重启时,键 k5、k6 的数据将丢失,而键 k1 至键 k4 的数据将被恢复。
bgsave 命令数据丢失
| 时间 | 事件 |
|---|---|
| T0 | 服务器开始运行 |
| T1 | 服务器执行 SET k1 v1 |
| T2 | 服务器执行 SET k2 v2 |
| T3 | 服务器执行 BGSAVE 命令,开始创建 RDB 文件 |
| T4 | 服务器执行 SET k3 v3 |
| T5 | RDB 文件创建完毕 |
| T6 | 服务器执行 SET k4 v4 |
| T7 | 服务器执行 BGSAVE 命令,开始创建 RDB 文件 |
| T8 | 服务器执行 SET k5 v5 |
| T9 | 服务器执行 SET k6 v6 |
| T10 | 服务器停机 |
- 因为 T7 创建的新 RDB 文件尚未完成,所以服务器在停机之后将使用 T5 成功创建的 RDB 文件进行数据恢复。
- 虽然服务器现有的 RDB 文件是在 T5 成功创建的,但由于这个文件是在 T3 开始创建的,所以它只包含了 T3 之前的数据,即键 k1 和键 k2 的数据。
- 基于上述原因,当服务器重启时,只有键 k1 和键 k2 的数据会被恢复,而键 k3 至键 k6 的数据则会丢失。
RDB 的优缺点
优点:
-
存储紧凑,节省内存空间。
-
恢复速度非常快。
-
适合全量备份、全量复制的场景,经常用于灾难恢复(对数据的完整性和一致性要求相对较低的场合)。
缺点:
- 容易丢失数据,容易丢失两次快照之间 Redis 服务器中变化的数据。
- RDB 通过 fork 子进程对内存快照进行全量备份,是一个重量级操作,频繁执行成本高。
Comments