--- title: Redis持久化机制详解 description: 深入解析Redis三种持久化机制RDB快照、AOF日志和混合持久化的工作原理、配置方法和优缺点对比,帮助你选择适合业务场景的持久化策略。 category: 数据库 tag: - Redis head: - - meta - name: keywords content: Redis持久化,RDB,AOF,混合持久化,bgsave,数据恢复,Redis备份,fork子进程 --- 使用缓存的时候,我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了做数据同步(比如 Redis 集群的主从节点通过 RDB 文件同步数据)。 Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式: - 快照(snapshotting,RDB) - 只追加文件(append-only file, AOF) - RDB 和 AOF 的混合持久化(Redis 4.0 新增) 官方文档地址: 。 ![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png) **本文基于 Redis 7.0+ 版本**。不同版本的持久化机制有重要差异,使用前请确认你的 Redis 版本: | 版本 | 持久化默认方式 | 重要特性 | | -------------- | -------------- | ----------------------- | | **Redis 4.0** | RDB | 引入 RDB+AOF 混合持久化 | | **Redis 6.0** | RDB | AOF 仍需手动开启 | | **Redis 7.0** | RDB | 引入 Multi-Part AOF | | **Redis 7.2+** | RDB | 进一步优化持久化性能 | **关键行为差异**: - **AOF rewrite 内存占用**:Redis 7.0 之前重写期间增量数据需在内存中保留,7.0+ 使用 Multi-Part AOF 解决 - **混合持久化**:Redis 4.0-6.x 需手动开启,Redis 7.0+ 默认启用。 检查你的 Redis 版本: ```bash redis-cli INFO server | grep redis_version # 输出示例:redis_version:7.0.12 ``` 下面这张图展示了 Redis 持久化机制的完整流程,包含了本文的核心内容: ![Redis 持久化机制完整流程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-persistence-flow.png) ## RDB 持久化 ### 什么是 RDB 持久化? Redis 可以通过创建快照来获得存储在内存里面的数据在 **某个时间点** 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。 快照持久化是 Redis 默认采用的持久化方式,在 `redis.conf` 配置文件中默认有此下配置: ```clojure # Redis 7.0 默认配置(单行格式) save 3600 1 300 100 60 10000 # 各条件含义: # - 3600 秒(1 小时)内至少有 1 个 key 变化 # - 300 秒(5 分钟)内至少有 100 个 key 变化 # - 60 秒(1 分钟)内至少有 10000 个 key 变化 # 等价于旧版多行格式: # save 3600 1 # save 300 100 # save 60 10000 ``` ### RDB 创建快照时会阻塞主线程吗? Redis 提供了两个命令来生成 RDB 快照文件: - `save` : 同步保存操作,会阻塞 Redis 主线程; - `bgsave` : fork 出一个子进程,子进程执行。 > 这里说 Redis 主线程而不是主进程的主要是因为 Redis 启动之后主要是通过单线程的方式完成主要的工作。如果你想将其描述为 Redis 主进程,也没毛病。 #### fork 性能开销分析 虽然 `bgsave` 在子进程中执行,不会阻塞主线程处理命令请求,但 **fork 操作本身是阻塞的**,且会带来额外的内存开销(下表中的为参考值,实际数值受到 CPU 性能、内存碎片率、系统负载等因素影响): | 数据集大小 | fork 延迟 | 额外内存占用 | 风险等级 | | ---------- | --------- | ---------------- | -------- | | < 1GB | < 10ms | ~10MB (页表复制) | 低 | | 1-10GB | 10-100ms | 10-100MB | 中 | | 10-50GB | 100ms-1s | 100-500MB | 高 | | > 50GB | > 1s | > 500MB | 极高 | > 本文以 RDB 的 `bgsave` 为例说明 fork 性能影响,但**同样的机制也适用于 AOF 重写(`BGREWRITEAOF` 命令)**。AOF 重写同样需要 fork 子进程,同样面临 fork 延迟、COW 内存开销和 THP 风险。生产环境中,无论是 RDB 还是 AOF 重写,都需要关注 fork 相关的性能指标。 #### Copy-on-Write (COW) 机制 - fork 后,子进程共享父进程的内存页(标准页 4KB) - 当父进程或子进程修改内存页时,内核复制该页(Copy-on-Write) - 大数据集 + 高写负载时,会导致大量页面复制,影响性能 #### THP(透明大页)导致的内存雪崩问题 Linux 发行版默认开启 **THP(Transparent Huge Pages,透明大页)**,大小为 2MB。THP 会增加大页被 COW 的概率,**最坏情况下**,如果内存被合并为 2MB 大页,即使客户端仅修改 10 字节的数据,内核也会复制完整的 2MB 内存页,导致 COW 的内存开销**放大 512 倍**(2MB / 4KB = 512)。 **实际行为**:内核不会强制所有内存都使用 2MB 大页,而是根据情况动态决定是否合并。只有在 THP 成功合并为大页后,修改才会触发 2MB 的 COW。但在高并发写入场景下,这仍会显著增加内存消耗,可能瞬间吸干宿主机内存,触发 **OOM Killer 强杀 Redis 进程**。 **验证方式**: ```bash cat /sys/kernel/mm/transparent_hugepage/enabled # 输出 [always] madvise never 表示已开启(危险!) # 应该输出 always madvise [never] ``` **解决方案**:在 Redis 启动脚本中添加 `echo never > /sys/kernel/mm/transparent_hugepage/enabled`,或使用 `redis-server --disable-thp yes`(Redis 6.0+ 支持)。 **启动警告**:Redis 检测到 THP 开启时会在启动日志中打印 `WARNING you have Transparent Huge Pages (THP) support enabled in your kernel`,必须立即处理。 #### 生产环境建议 ```bash # 1. 监控 fork 风险指标 redis-cli INFO memory | grep -E "(used_memory|used_memory_rss)" # 输出示例: # used_memory:1073741824 # used_memory_rss:1226833920 # used_memory_rss_human:1.14G # 计算 RSS/USED 比值,fork 时应 < 2 # 如果接近或超过 2,说明 fork 风险高 # 2. 设置 maxmemory 限制 Redis 内存占用,为 fork 预留空间 # 在 redis.conf 中设置: # maxmemory 8gb # maxmemory-policy allkeys-lru # 3. 避免在高峰期手动触发 BGSAVE # 让 Redis 根据配置规则自动触发 # 4. 考虑主从复制 + 从节点持久化架构 # 将持久化操作转移到从节点,避免主节点 fork 开销 ``` **监控告警**: - `rdb_last_bgsave_time_sec`:上次 bgsave 耗时,应 < 5s - `rdb_last_cow_size`:上次 fork 的 COW 内存大小,应 < 10% `used_memory` ## AOF 持久化 ### 什么是 AOF 持久化? 与快照持久化相比,AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 `appendonly` 参数开启: > **版本说明**:Redis 默认使用 RDB 持久化方式。若需使用 AOF,需要手动设置 `appendonly yes`。Redis 7.0 引入了 Multi-Part AOF 机制优化 AOF 性能,但并未改变默认持久化方式。 ```bash appendonly yes ``` 开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( `fsync`策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。 只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险,比如说:系统内核缓存区的数据还未同步,磁盘机器就宕机了,那这部分数据就算丢失了。 AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 `dir` 参数设置的,默认的文件名是 `appendonly.aof`。 ### AOF 工作基本流程是怎样的? AOF 持久化功能的实现可以简单分为 5 步: 1. **命令追加(append)**:所有的写命令会追加到 AOF 缓冲区中。 2. **文件写入(write)**:将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用`write`函数(系统调用),`write`将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。 3. **文件同步(fsync)**:这一步才是持久化的核心!根据你在 `redis.conf` 文件里 `appendfsync` 配置的策略,Redis 会在不同的时机,调用 `fsync` 函数(系统调用)。`fsync` 针对单个文件操作,对其进行强制硬盘同步(文件在内核缓冲区里的数据写到硬盘),`fsync` 将阻塞直到写入磁盘完成后返回,保证了数据持久化。 4. **文件重写(rewrite)**:随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。 5. **重启加载(load)**:当 Redis 重启时,可以加载 AOF 文件进行数据恢复。 > Linux 系统直接提供了一些函数用于对文件和设备进行访问和控制,这些函数被称为 **系统调用(syscall)**。 这里对上面提到的一些 Linux 系统调用再做一遍解释: - `write`:写入系统内核缓冲区之后直接返回(仅仅是写到缓冲区),不会立即同步到硬盘。虽然提高了效率,但也带来了数据丢失的风险。**同步硬盘操作取决于 Linux 内核的脏页回写策略(Dirty Page Writeback)**,主要受以下参数影响: - `/proc/sys/vm/dirty_expire_centisecs`:脏页过期时间(默认 30 秒) - `/proc/sys/vm/dirty_writeback_centisecs`:内核回写线程的唤醒间隔(默认 5 秒) - 系统内存压力:内存不足时会更积极触发同步 - **这意味着 `appendfsync no` 模式下宕机时,可能丢失的数据量是不可控且不可预测的**,取决于上次内核同步的时间点。 - `fsync`:`fsync`用于强制刷新系统内核缓冲区(同步到磁盘),确保写磁盘操作结束才会返回。 AOF 工作流程图如下: ![AOF 工作基本流程](https://oss.javaguide.cn/github/javaguide/database/redis/aof-work-process.png) ### AOF 持久化方式有哪些? 在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是: 1. `appendfsync always`:主线程调用 `write` 执行写操作后,会立刻调用 `fsync` 函数同步 AOF 文件(刷盘)。主线程会阻塞,直到 `fsync` 将数据完全刷到磁盘后才会返回。这种方式数据最安全,理论上不会有任何数据丢失。但因为每个写操作都会同步阻塞主线程,所以性能极差。 2. `appendfsync everysec`:主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)。这种方式主线程的性能基本不受影响。在性能和数据安全之间做出了绝佳的平衡。不过,在 Redis 异常宕机时,通常可能丢失最近 1 秒内的数据。 > **生产级真相(2 秒丢失与阻塞风险)**: > > "最多丢失 1 秒"是理想情况。当磁盘 I/O 繁忙时,后台 fsync 执行时间过长,主线程在执行写命令时会检查上一次 fsync 的完成时间。如果距离上次成功 fsync 超过 2 秒,主线程将被**强制阻塞**以保护内存不被撑爆(Redis 源码 `aof.c` 中的 `aof_background_fsync` 阻塞判断逻辑)。 > > 因此,**极端宕机情况下,可能会丢失最多 2 秒的数据**,且磁盘抖动会直接导致 Redis P99 延迟飙升。 > > **必须监控指标**:`redis-cli INFO persistence | grep aof_delayed_fsync`(记录主线程被 fsync 阻塞的累计次数,只有启用了 AOF 才有这个字段)。 3. `appendfsync no`:主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。 这种方式性能最好,因为避免了 `fsync` 的阻塞。但数据安全性最差,宕机时丢失的数据量不可控,取决于操作系统上一次同步的时间点。 可以看出:**这 3 种持久化方式的主要区别在于 `fsync` 同步 AOF 文件的时机(刷盘)**。 为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能受到的影响较小。通常情况下,即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 > ⚠️ **注意**:当磁盘 I/O 瓶颈严重时,Redis 主线程可能因等待 fsync 而阻塞长达 2 秒,期间数据丢失窗口扩大至 2 秒。生产环境应监控 `aof_delayed_fsync` 指标来评估磁盘健康度。 从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为: - BASE:表示基础 AOF 文件,它一般由子进程通过重写产生,该文件最多只有一个。 - INCR:表示增量 AOF 文件,它一般会在 AOFRW 开始执行时被创建,该文件可能存在多个。 - HISTORY:表示历史 AOF 文件,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR AOF 都将变为 HISTORY,HISTORY 类型的 AOF 会被 Redis 自动删除。 Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开发者的[Redis 7.0 Multi Part AOF 的设计和实现](https://zhuanlan.zhihu.com/p/467217082) 这篇文章。 **相关 issue**:[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)。 ### AOF 为什么是在执行完命令之后记录日志? 关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。 ![AOF 记录日志过程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-aof-write-log-disc.png) **为什么是在执行完命令之后记录日志呢?** - 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查; - 在命令执行完之后再记录,不会阻塞当前的命令执行。 这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过): - 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失; - 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。 ### AOF 重写了解吗? 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。 ![AOF 重写](https://oss.javaguide.cn/github/javaguide/database/redis/aof-rewrite.png) > AOF 重写(rewrite) 是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。 由于 AOF 重写会进行大量的写入操作,为了避免对 Redis 正常处理命令请求造成影响,Redis 将 AOF 重写程序放到子进程里执行。 AOF 文件重写期间,Redis 还会维护一个 **AOF 重写缓冲区**,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。 开启 AOF 重写功能,可以调用 `BGREWRITEAOF` 命令手动执行,也可以设置下面两个配置项,让程序自动决定触发时机: - `auto-aof-rewrite-min-size`:如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB; - `auto-aof-rewrite-percentage`:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。 **AOF rewrite 的失败边界与风险场景**: 虽然 AOF rewrite 放在子进程执行,但仍存在以下风险需要了解: | 风险场景 | 影响 | 触发条件 | 应对措施 | | ---------------- | --------------------------- | ------------------------ | ------------------------------------------- | | **fork 失败** | 无法创建 rewrite 子进程 | 内存不足、系统限制 | 监控内存使用率,设置 `maxmemory` | | **磁盘满** | 新 AOF 文件写入失败 | rewrite 期间数据量增长快 | 监控磁盘使用率(`df -h`),设置告警阈值 70% | | **inode 耗尽** | 无法创建新文件 | 小文件过多的系统 | 监控 inode 使用率(`df -i`),清理临时文件 | | **时间戳回拨** | Multi-Part AOF 文件管理混乱 | 虚拟机时钟同步问题 | 配置 NTP 服务,设置 `aof-timestamp-enabled` | | **SIGTERM 信号** | rewrite 被中断 | 运维人员手动重启 | 配置优雅关闭(`shutdown-timeout`) | **生产环境监控建议**: ```bash # 监控 AOF rewrite 状态 redis-cli INFO persistence | grep aof_rewrite_in_progress # 监控 AOF 文件大小增长 redis-cli INFO persistence | grep aof_current_size redis-cli INFO persistence | grep aof_base_size # 检查磁盘和 inode 使用率 df -h /var/lib/redis df -i /var/lib/redis # 设置 AOF rewrite 期间增量 fsync 策略(Redis 7.0+) # aof-rewrite-incremental-sync yes ``` Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。 Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的[从 Redis7.0 发布看 Redis 的过去与未来](https://mp.weixin.qq.com/s/RnoPPL7jiFSKkx3G4p57Pg) 这篇文章。 > AOF 重写期间的增量数据如何处理一直是个问题,在过去写期间的增量数据需要在内存中保留,写结束后再把这部分增量数据写入新的 AOF 文件中以保证数据完整性。可以看出来 AOF 写会额外消耗内存和磁盘 IO,这也是 Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。 > > 阿里云的 Redis 企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了 Multi-part AOF 机制来解决,同时也贡献给了社区并随此次 7.0 发布。具体方法是采用 base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和 IO 资源的浪费,同时也支持对历史 AOF 文件的保存管理,结合 AOF 文件中的时间信息还可以实现 PITR 按时间点恢复(阿里云企业版 Tair 已支持),这进一步增强了 Redis 的数据可靠性,满足用户数据回档等需求。 **相关 issue**:[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)。 ### AOF 文件如何验证数据完整性? **核心结论**:纯 AOF 文件**没有**校验和机制,仅通过逐条命令解析验证;CRC64 校验和仅存在于混合持久化文件的 **RDB 部分**。 #### 纯 AOF 模式:无校验和,仅语法解析 纯 AOF 文件不会对整体或单条命令计算 CRC64 校验和,而是通过逐条解析文件中的命令来验证有效性。 **为什么没有校验和?** AOF 是高频追加写入的文本日志。如果每次追加命令都要重新计算整个文件的 CRC64 校验和,会对主线程的 CPU 和磁盘 I/O 造成严重拖累。因此 Redis 选择了更轻量的方式:重启加载时逐条读取并解析命令语法。 如果解析过程中发现语法错误(如命令不完整、格式错误),Redis 会终止加载并报错。 > **尾部截断容灾(自动恢复)**: > > 在遭遇意外断电或 `kill -9` 强制终止时,AOF 文件的最后一条命令极可能写入不完整(只写了一半)。此时的恢复行为由 **`aof-load-truncated`** 配置决定: > > | 配置值 | 行为 | 适用场景 | > | ------------- | ------------------------------------------------------------------------------- | ---------------------------------------- | > | `yes`(默认) | Redis 自动丢弃文件尾部不完整的命令,继续完成启动并在日志中打印警告信息 | 生产环境推荐,允许少量数据丢失换取可用性 | > | `no` | Redis 拒绝启动并直接报错,强制要求人工使用 `redis-check-aof` 工具确认并修复数据 | 金融等对数据完整性要求极高的场景 | > > **验证截断恢复**: > > ```bash > # 模拟断电场景:向 AOF 文件追加无意义的乱码 > echo "truncated garbage data" >> /var/lib/redis/appendonly.aof > > # 重启 Redis(aof-load-truncated=yes 时会自动恢复) > redis-server /path/to/redis.conf > # 日志输出:# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix > ``` > > **失败模式**:如果 AOF 文件的**中间部分**(而非尾部)因为磁盘静默损坏出现乱码,自动截断机制无效,Redis 将直接宕机拒绝服务。此时需要使用 `redis-check-aof --fix` 工具修复。 **redis-check-aof 工作原理**: - **检测阶段**:根据 AOF 文件格式逐一读取命令,判断命令参数个数、参数字符串长度等,提供错误/不完整命令的文件位置 - **修复阶段**:从错误位置截断后续文件内容(**注意:会丢失截断点之后的所有数据**),原文件会被备份为 `appendonly.aof.broken` #### 混合持久化模式:分段校验策略 在 **混合持久化模式**(Redis 4.0 引入)下,AOF 文件采用"分段治理"的校验策略: ``` ┌─────────────────────────────────────────────────────────┐ │ 混合持久化文件结构 │ ├─────────────────────────────────────────────────────────┤ │ RDB 快照部分(二进制) ← CRC64 校验和保护这部分 │ │ ├── "REDIS" 头部 │ │ ├── 数据库编号、键值对... │ │ ├── EOF 标志 │ │ └── CRC64 校验和(8 字节) ← 校验边界在这里 │ ├─────────────────────────────────────────────────────────┤ │ AOF 增量部分(文本) ← 无校验和,仅语法解析 │ │ ├── *3\r\n$3\r\nSET\r\n... │ │ └── ... │ └─────────────────────────────────────────────────────────┘ ``` - **RDB 快照部分**:以固定的 `REDIS` 字符开头,存储某一时刻的内存数据快照,并在快照数据末尾附带一个 CRC64 校验和。这个校验和**严格卡在 RDB 数据块的末尾**,仅保障这部分二进制快照的完整性。 - **AOF 增量部分**:紧随 RDB 快照之后,记录增量写命令。这部分**依然没有校验和**,采用与纯 AOF 相同的逐条语法解析验证。 **加载时的校验流程**: 1. Redis 首先校验 RDB 快照部分:计算该部分数据的 CRC64 校验和,与存储的校验和值比较。如果不匹配,Redis 拒绝启动。 2. RDB 部分校验通过后,逐条解析 AOF 增量命令。解析出错则停止加载后续命令(但此时 RDB 快照数据已成功加载)。 #### 配置项说明 | 配置项 | 作用域 | 说明 | | -------------------- | -------------------------------------- | -------------------------------------------------- | | `rdbchecksum` | RDB 文件、混合持久化的 RDB 部分 | 控制是否计算 CRC64 校验和,对纯 AOF 增量部分不生效 | | `aof-load-truncated` | 纯 AOF 文件、混合持久化的 AOF 增量部分 | 控制尾部截断时是否自动丢弃并继续启动 | **人工修补**(高级用户): - 如果不想通过截断来修复 AOF 文件,可以尝试人工修补 - 使用文本编辑器打开 AOF 文件(纯文本格式),手动删除或修复错误命令 - 适用于明确知道错误位置的特定场景 ## 新版本优化 ### Redis 4.0 对于持久化机制做了什么优化? 由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化。 #### 配置说明 ```bash # 开启 AOF appendonly yes # 开启混合持久化(Redis 7.0+ 默认启用) aof-use-rdb-preamble yes # 优化重写触发条件 auto-aof-rewrite-percentage 100 # AOF 文件大小比上次重写后增长 100% 时触发 auto-aof-rewrite-min-size 64mb # AOF 文件至少达到 64MB 才触发重写 ``` **版本差异**: - **Redis 4.0-6.x**:混合持久化默认关闭,需手动配置 `aof-use-rdb-preamble yes` - **Redis 7.0+**:混合持久化**默认启用**,无需额外配置 #### 工作原理 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。 **混合持久化文件结构**: ``` ┌───────────────────┐ │ RDB Header │ ← 二进制快照(压缩格式) │ REDIS0009 │ │ ... │ ├───────────────────┤ │ AOF Log Entries │ ← 文本格式命令 │ *3\r\n$3\r\nSET\r\n$5\r\nkey01\r\n... │ INCR counter │ │ ... │ └───────────────────┘ ``` **核心工作流程**: 1. **写处理阶段**: - 客户端执行写命令(`SET/INCR` 等) - Redis 立即更新内存数据 - 将命令追加到 AOF 缓冲区(文本格式) 2. **持久化触发阶段**: - AOF 文件大小达到阈值(默认 64MB)或增长 100% - 触发 AOF 重写(`BGREWRITEAOF`) 3. **文件构建阶段**: - 子进程将当前内存数据以 RDB 格式写入新 AOF 文件开头 - 父进程继续处理写命令,增量数据记录到重写缓冲区 - 重写完成后,将重写缓冲区的增量命令追加到新 AOF 文件末尾 4. **数据恢复阶段**: - Redis 启动时优先加载 RDB 部分(快速恢复基础数据) - 然后顺序重放 AOF 增量命令(恢复最新数据) #### 优势对比 | 指标 | 纯 RDB | 纯 AOF | 混合持久化 | | ---------------- | ------------ | -------------- | -------------- | | **恢复速度** | 快(秒级) | 慢(分钟级) | 快(秒级) | | **数据丢失窗口** | 分钟级 | ≤2 秒 | ≤2 秒 | | **文件大小** | 小(压缩) | 大(文本日志) | 中等 | | **写入影响** | 低 | 高 | 中等 | | **可读性** | 差(二进制) | 好(文本) | 差(RDB 部分) | **基准数据**(1GB 数据集,SSD): - 纯 AOF 恢复:30-60 秒 - 混合持久化恢复:2-5 秒(**快 5-10 倍**) **混合持久化缺点**: - AOF 文件里面的 RDB 部分是压缩格式,不再是 AOF 格式,可读性较差。 - 需要额外消耗 CPU 进行 RDB 压缩和解压。 #### 常见问题及解决方案 **1. 配置验证**: ```bash # 方法 1:检查文件头(输出 REDIS 表示启用了混合持久化) head -c 5 appendonly.aof # 方法 2:CLI 验证 redis-cli CONFIG GET aof-use-rdb-preamble # 输出:1) "aof-use-rdb-preamble" # 2) "yes" ``` **2. 文件损坏恢复**: **工具说明**: | 工具 | 工作原理 | 错误检测 | 修复功能 | | ------------------- | ----------------------------------------------------------------- | ------------------------------------ | --------------------------------------------------- | | **redis-check-aof** | 根据 AOF 文件格式逐一读取命令,判断命令参数个数、参数字符串长度等 | 检测命令正确性和完整性,提供错误位置 | ✅ **支持修复**:从错误位置截断后续内容,或人工修补 | | **redis-check-rdb** | 按照 RDB 文件格式依次读取文件头、数据部分、文件尾 | 在读取过程中判断内容是否正确并报错 | ❌ **不支持修复**:仅检测问题,需人工修复 | **恢复步骤**: ```bash # 步骤 1:检测 AOF 文件问题 redis-check-aof appendonly.aof # 输出错误位置和原因 # 步骤 2:修复 AOF 文件(从错误位置截断) redis-check-aof --fix appendonly.aof # 原 AOF 文件会被备份为 appendonly.aof.broken # 步骤 3:检测 RDB 部分 redis-check-rdb appendonly.aof # 仅检测,不支持 --fix 参数 # 步骤 4:如果 RDB 部分有问题,需人工修复或丢弃整个文件 # 选项 A:人工修复(需了解 RDB 二进制格式) # 选项 B:删除混合持久化文件,仅使用纯 RDB 或纯 AOF 恢复 # 步骤 5:启动 Redis redis-server --appendonly yes --appendfilename appendonly.aof ``` > **⚠️ 重要提示**: > > - **AOF 文件**:`redis-check-aof --fix` 会从错误位置截断文件,**丢失截断点之后的所有数据** > - **RDB 文件**:`redis-check-rdb` **不支持修复**,如果 RDB 部分损坏,整个混合持久化文件无法恢复,只能依赖备份或纯 AOF 文件 > - **人工修复**:对于 RDB 部分,如果必须修复,需要使用十六进制编辑器(如 `hexdump`、`xxd`)手动修改二进制格式 #### 生产配置建议 ```bash # 完整生产配置示例 appendonly yes aof-use-rdb-preamble yes # 性能优化 aof-rewrite-incremental-fsync yes # 增量 fsync,减少磁盘 I/O 峰值 # 延迟敏感场景(推荐 yes) no-appendfsync-on-rewrite yes # 重写期间暂停 fsync,避免阻塞 # 数据安全场景(推荐 no) no-appendfsync-on-rewrite no # 重写期间仍执行 fsync,可能阻塞但更安全 # 容量规划建议: # - 预留 2x 内存作为磁盘空间 # - 保持单个 AOF 文件 < 16GB # - 监控 aof_delayed_fsync 指标 ``` 官方文档地址: ![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png) ### Redis 7.0 对于持久化机制做了什么优化? 由于 AOF 重写过程中存在内存缓冲增量数据和磁盘双写的问题,于是,Redis 7.0 开始支持 Multi-Part AOF(默认启用,可以通过配置项 `appenddirname` 指定目录)。 如果把 Multi-Part AOF 启用,AOF 文件将被拆分为 base 文件(最多一个,初始全量快照,可为 RDB 或 AOF 格式)和多个 incr 文件(增量命令日志),重写期间新增命令直接写入新的 incr 文件,由 manifest 文件跟踪所有部分。这样做的好处是可以消除重写时的内存缓冲开销和双重 I/O 写入,提高性能并减少潜在的 fsync 阻塞。由于文件结构分离,INCR 文件在重写前保持只读,单文件拷贝相对安全;但跨文件的一致性备份仍需暂停重写,整体备份流程比单文件 AOF 更复杂,且在极大数据集下仍可能需监控资源。 > **核心单点故障风险:manifest 文件损坏** > > Multi-Part AOF 依赖 **manifest 文件**来跟踪和管理所有 `base/incr/history` 文件,这是整个增量日志体系的核心元数据。如果 manifest 文件损坏或丢失: > > | 风险场景 | 影响 | 恢复难度 | > | ------------------------------ | ------------------------------------------------------- | --------------------------- | > | **manifest 静默损坏** | Redis 启动时无法正确识别和加载 AOF 文件,数据库无法恢复 | 极高(需手动重建 manifest) | > | **磁盘故障导致 manifest 丢失** | 即使 base/incr 文件完整,Redis 也无法重构文件依赖关系 | 极高(需人工干预) | > > **缓解措施**: > > ```bash > # 1. 备份 manifest 文件(与数据文件同等重要) > cp /var/lib/redis/appendonlydir/appendonly.aof.manifest /backup/ > > # 2. 监控磁盘健康度(提前发现故障) > smartctl -a /dev/sda | grep -E "SMART overall-health self-assessment|Media_Errors" > > # 3. 定期验证 manifest 完整性(Redis 启动时会自动校验) > redis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.manifest > ``` > > **官方未提供自动化修复工具**,生产环境必须将 manifest 文件纳入备份策略,其重要性等同于 RDB/AOF 数据文件本身。 ## 生产环境监控指标 ### 持久化性能指标 ```bash # RDB 相关指标 redis-cli INFO persistence | grep rdb_last_bgsave_time_sec # 建议:< 5s。超过 5s 说明数据集过大或 I/O 性能瓶颈 redis-cli INFO persistence | grep rdb_last_cow_size # 建议:< 10% used_memory。超过说明 fork 的 Copy-on-Write 内存开销大 redis-cli INFO memory | grep used_memory_rss redis-cli INFO memory | grep used_memory # 计算:used_memory_rss / used_memory,fork 时应 < 2 # AOF 相关指标 redis-cli INFO persistence | grep aof_rewrite_in_progress # 期望:0(未在重写)或 1(正在重写) redis-cli INFO persistence | grep aof_current_size redis-cli INFO persistence | grep aof_base_size # 监控增长率,避免 rewrite 过于频繁 redis-cli INFO persistence | grep aof_buffer_length # 建议:< 4MB。过大说明主线程写入速度快于 fsync 速度 ``` ### 系统资源监控 ```bash # 磁盘使用率和 I/O 等待 iostat -x 1 5 | grep dm-0 # 关注:%util(I/O 使用率)、await(平均等待时间) # 磁盘空间(预留空间给 rewrite 生成新文件) df -h /var/lib/redis # 建议:使用率 < 70% # inode 使用率(小文件多的场景) df -i /var/lib/redis # 建议:使用率 < 90% # 内存使用率 free -h # 建议:为 fork 预留至少 20% 空闲内存 ``` ### 告警规则建议 > **指标来源说明**: > > - **Redis 指标**:通过 `redis-cli INFO` 或 Redis exporter 获取(如 `redis_rss_memory`、`aof_current_size`) > - **节点级指标**:通过 node_exporter 或系统命令获取(如 `disk_usage`、系统内存、CPU 使用率) > > 以下告警规则假设使用 Prometheus + Redis exporter + node_exporter 监控体系。 ```yaml alert_rules: # ── Redis 持久化相关告警 ──────────────────────────────────────── - name: "RedisHighMemFragmentation" expr: redis_memory_rss_bytes / redis_memory_used_bytes > 2 for: 5m labels: severity: warning annotations: summary: "Redis 内存碎片率过高,fork COW 风险上升" description: > 实例 {{ $labels.instance }} 的 mem_fragmentation_ratio = {{ $value | humanize }}, 超过阈值 2。碎片率过高意味着 OS 实际分配的物理页远多于 Redis 自身统计, 执行 BGSAVE / BGREWRITEAOF 触发 fork 后,COW 需复制的页数会显著增加, 在高写入负载下可能导致内存暴涨,OOM 风险上升。 建议执行 MEMORY PURGE 或在低峰期重启实例整理碎片。 - name: "RedisAofGrowthTooFast" expr: deriv(redis_aof_current_size_bytes[5m]) * 60 > 10485760 for: 5m labels: severity: warning annotations: summary: "Redis AOF 文件写入速率过高" description: > 实例 {{ $labels.instance }} 的 AOF 增长速率超过 10 MB/min (当前约 {{ $value | humanize1024 }}B/min)。 高速写入会持续触发 auto-aof-rewrite,加剧磁盘 I/O 压力, 并可能产生写入放大。建议检查业务是否存在大量小命令风暴或 KEYS 类全量扫描。 - name: "RedisAofFsyncDelayed" expr: rate(redis_aof_delayed_fsync_total[5m]) > 0 for: 2m labels: severity: critical annotations: summary: "Redis AOF fsync 延迟,主线程响应受阻" description: > 实例 {{ $labels.instance }} 持续出现 aof_delayed_fsync 增长, 主线程因等待 AOF fsync 完成而被阻塞,直接导致命令响应 P99 劣化。 常见原因:① 磁盘 I/O 带宽饱和;② appendfsync 设置为 always; ③ 与其他高 I/O 进程共用磁盘。建议切换为 everysec 策略或迁移至独立磁盘。 # ── 节点级资源告警 ───────────────────────────────────────────── - name: "RedisDiskUsageHigh" expr: > (1 - node_filesystem_avail_bytes{mountpoint="/var/lib/redis"} / node_filesystem_size_bytes{mountpoint="/var/lib/redis"}) * 100 > 70 for: 5m labels: severity: warning annotations: summary: "Redis 数据盘使用率超过 70%" description: > 挂载点 /var/lib/redis 当前使用率为 {{ $value | humanize }}%。 AOF rewrite 期间会临时生成新文件,需预留约 1.5x 当前 AOF 大小的空间, 磁盘不足将导致 rewrite 失败并触发 Redis 错误日志 "MISCONF"。 RDB bgsave 同理。 remediation: > 1. 清理过期 RDB 快照与历史 AOF 文件; 2. 调高 auto-aof-rewrite-min-size 降低 rewrite 频率; 3. 磁盘扩容或将数据目录迁移至更大分区。 ``` ## 如何选择 RDB 和 AOF? 关于 RDB 和 AOF 的优缺点,官网上面也给了比较详细的说明[Redis persistence](https://redis.io/docs/manual/persistence/),这里结合自己的理解简单总结一下。 **RDB 比 AOF 优秀的地方**: - **文件紧凑,适合备份和灾难恢复**:RDB 文件存储的内容是经过压缩的二进制数据,保存着某个时间点的数据集,文件很小,非常适合做数据的备份和灾难恢复。AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会比 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF,新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过,Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。 - **恢复速度快**:使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。 - **主从复制优势**:在副本(replica)上,RDB 支持重启和故障转移后的**部分重新同步**(Partial Resynchronization)。副本可以使用 RDB 快照快速同步到主节点的某个时间点状态,而不需要全量同步。 - **性能开销小**:RDB 最大化 Redis 性能,因为 Redis 父进程需要做的唯一持久化工作就是 fork 子进程,子进程将完成所有其余工作。父进程永远不会执行磁盘 I/O 或类似操作。 **AOF 比 RDB 优秀的地方**: - **数据安全性更高,支持秒级持久化**:RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。生成 RDB 文件的过程是比较繁重的,虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。AOF 支持秒级数据丢失(取决于 `fsync` 策略,如果是 `everysec`,通常最多丢失 1 秒的数据;但磁盘 I/O 繁忙时可能丢失 2 秒且主线程会阻塞),仅仅是追加命令到 AOF 文件,操作轻量。 - **版本兼容性好**:RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。 - **可读性和可操作性强**:AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,也可以直接操作 AOF 文件来解决一些问题。比如,如果执行`FLUSHALL`命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。 - **追加日志无损坏风险**:AOF 日志是追加日志,没有寻道,也没有断电损坏问题。即使日志由于某种原因(磁盘已满或其他原因)以半写入命令结尾,`redis-check-aof` 工具也能轻松修复。 **版本演进对选型的影响**: | 版本 | 关键改进 | 对 AOF 的影响 | 对选型的意义 | | ------------- | ---------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------- | | **Redis 4.0** | 引入混合持久化(`aof-use-rdb-preamble`) | AOF 重写时 base 文件使用 RDB 格式,恢复速度提升 5-10 倍 | 缓解了纯 AOF 加载慢的问题,但仍需关注重写期间的内存和 I/O 开销 | | **Redis 7.0** | 引入 Multi-Part AOF | 彻底消除重写期间的双写问题,内存和 I/O 开销大幅降低 | 单独使用 AOF 在生产环境更具可行性,但 fork 阻塞问题仍未解决 | **未解决的核心问题**: - **fork 阻塞**:无论是 RDB bgsave 还是 AOF 重写,fork 操作本身都会阻塞主线程(数据集越大,阻塞时间越长) - **官方建议**:Redis 官方文档至今仍建议**同时开启 RDB 和 AOF**,RDB 作为额外的冷备手段,应对 AOF 文件损坏或写入错误等极端场景 **AOF 和 RDB 的交互**: 当 AOF 和 RDB 持久化同时启用时: - **避免同时进行重 I/O 操作**:Redis 2.4+ 确保避免在 RDB 快照进行时触发 AOF 重写,或允许在 AOF 重写期间进行 BGSAVE。这防止两个 Redis 后台进程同时进行繁重的磁盘 I/O。 - **AOF 重写调度**:当快照正在进行且用户显式请求日志重写操作(使用 BGREWRITEAOF)时,服务器将返回 OK 状态码,告诉用户操作已调度,重写将在快照完成后开始。 - **重启恢复优先级**:如果 AOF 和 RDB 持久化都启用且 Redis 重启,**AOF 文件将用于重建原始数据集**,因为它被保证是最完整的。 **选型建议**: | 场景 | 推荐方案 | 说明 | | -------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------- | | **纯缓存(可丢失)** | **关闭持久化** 或仅 RDB(低频) | 完全关闭开销最小;若需冷备则保留低频 RDB | | **数据重要性中等**(会话、配置) | **RDB + AOF 混合持久化**(Redis 4.0+) | RDB 加速恢复,AOF 增量补充,`everysec` 最多丢 1s | | **数据重要性高**(业务核心数据) | **RDB + AOF(MP-AOF,Redis 7.0+)**,且 Redis 作为缓存层而非唯一存储 | MP-AOF 降低重写开销;真正的持久化由主数据库(MySQL 等)负责 | | **主从架构** | **主节点关闭持久化,从节点开启 AOF** | 主节点禁止配置自动重启,防止空数据集覆盖从节点 | ## 参考 - 《Redis 设计与实现》 - Redis persistence - Redis 官方文档: - The difference between AOF and RDB persistence: - Redis AOF 持久化详解 - 程序员历小冰: - Redis RDB 与 AOF 持久化 · Analyze: