RDB 持久化详解

标签:Redis首次发布:2023-11-17最近修改:2026-08-15
摘要

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 不能处理任何读写请求,无法对外提供服务。

bash
127.0.0.1:6379> saveOK

BGSAVE:以非阻塞方式创建 RDB 文件

通过在 redis-cli 客户端中执行 bgsave 命令可立即进行一次持久化保存。不同于 save 命令的是,正如该命令的名称一样,background save,后台运行 save。bgsave 命令会使服务器进程 redis-server 生成一个子进程,由该子进程负责完成保存过程。在子进程进行保存过程中,不会阻塞 redis-server 进程对客户端读写请求的处理。

bash
127.0.0.1:6379> bgsaveBackground saving started

需要注意的是由于执行 BGSAVE 命令需要创建子进程,所以父进程占用的内存数量越大,创建子进程这一操作耗费的时间也会越长,因此 Redis 服务器在执行 BGSAVE 命令时,仍然可能会由于创建子进程而被短暂地阻塞。

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

自动条件触发的本质仍是 bgsave 命令的执行。只不过是用户通过在配置文件中做相应的设置后,Redis 会根据设置信息自动调用 bgsave 命令执行。

bash
# 除非另有指定,否则默认情况下 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

持久化策略按照如下顺序进行:

  1. 如果服务器在 60 秒之内,对数据库进行了至少 10000 次修改,则进行持久化。
  2. 如果服务器在 300 秒之内,对数据库进行了至少 100 次修改,则进行持久化。
  3. 如果服务器在 3600 秒(一小时)之内,对数据库进行了至少 1 次修改,则进行持久化。

查看最近持久化时间

通过 lastsave 命令可以查看最近一次执行持久化的时间,其返回的是一个 Unix 时间戳。

bash
127.0.0.1:6379> lastsave(integer) 1786795674

RDB 优化配置

RDB 相关的配置在 redis.conf 文件的 SNAPSHOTTING 部分。

  1. save
    该配置用于设置快照的自动保存触发条件,即 save point,保存点。该触发条件是在指定时间段内发生了指定次数的写操作。如果不启用 RDB 持久化,只需设置 save 的参数为空串即可:save ""

  2. 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 将自动允许再次写入。

  3. 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,降低性能,但可大幅降低磁盘文件的大小,方便保存到磁盘,加速主从集群中从节点的数据同步。

  4. 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
  5. 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
  6. dbfilename

    bash
    # 用于保存数据库的文件名# ---------------------------------# The filename where to dump the DBdbfilename dump.rdb
  7. 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 持久化功能都未开启时才生效。

  8. 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 文件整体上可分为三个区域:头信息区、数据区、尾信息区。

rdb_binary_layout_white

1. 头部信息区

  1. 魔数

    以 ascii 码 “REDIS” 开头。用来验证当前文件的格式。类似 java 程序编译后的 class 文件,以 “CAFEBABE” 魔数开头,寓意 java 和 cafe 的千丝万缕的关系。

  2. RDB version

    表示 RDB 文件的版本。并且高版本 redis 可以 100% 向后兼容加载旧版 rdb 文件。版本信息使用 4 个字节表示。 如:30 30 30 37 转换为 ascii 码 00 00 00 07,代表 rdb version 文件为第 7 版。

  3. 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. 数据区

  1. SELECTDB

    常量的长度为 1 字节。当读入程序遇到这个值的时候, 它知道接下来要读入的将是一个数据库号码。

  2. db_number

    保存着一个数据库号码, 根据号码的大小不同, 这个部分的长度可以是 1 字节、 2 字节或者 5 字 B 节。 当程序读入 db_number 部分之后, 服务器会调用 SELECT 命令, 根据读入的数据库号码进行数据库切换, 使得之后读入的键值对可以载入到正确的数据库中。

  3. key_value_pairs

    保存了数据库中的所有键值对数据。

    不带过期时间的键值对:

    • TYPE 记录了 value 的类型, 长度为 1 字节, 值可以是以下常量的其中一个:

      • REDIS_RDB_TYPE_STRING
      • REDIS_RDB_TYPE_LIST
      • REDIS_RDB_TYPE_SET
      • REDIS_RDB_TYPE_ZSET
      • REDIS_RDB_TYPE_HASH
      • REDIS_RDB_TYPE_LIST_ZIPLIST
      • REDIS_RDB_TYPE_SET_INTSET
      • REDIS_RDB_TYPE_ZSET_ZIPLIST
      • REDIS_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 持久化流程:

rdb_persistence_process

关键执行步骤如下

  1. Redis 父进程首先判断:当前是否在执行 save,或 bgsave/bgrewriteaof 的子进程,如果在执行则 bgsave 命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。

  2. 父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的,Redis 不能执行来自客户端的任何命令。父进程 fork 后,bgsave 命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令。

  3. 子进程进程对内存数据生成快照文件。

  4. 父进程在此期间接收的新的写操作,使用 COW 机制写入。

  5. 子进程完成快照写入,替换旧 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) 机制对数据段页面进行分离,也就是复制一块内存出来给主进程去修改。

redis_cow_mechanism

数据丢失情况分析

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 子进程对内存快照进行全量备份,是一个重量级操作,频繁执行成本高。
rdb_save_load

Comments

评论区将在滚动到这里时加载。