--- tags: [Redis, 缓存, 持久化, AOF] create time: 2026-05-15 18:13 --- # AOF 持久化 ## 概述 AOF(Append Only File)以**追加写日志**的方式记录每个写操作。相比 RDB 的快照式恢复,AOF 提供更高的数据安全性——你可以根据策略做到**不丢数据、丢 1 秒、或丢 N 条**。代价是文件更大、恢复更慢。 ## 核心机制 ### 命令追加过程 ```mermaid flowchart LR Client["客户端写入"] --> Server["Redis 主线程
执行命令 + 返回结果"] Server --> Buffer["aof_buffer
内核/用户态缓冲区"] Buffer --> FSyncPolicy{"刷新策略?"} FSyncPolicy -->|everysec| KernelBuf["操作系统页缓存"] KernelBuf --> Disk["磁盘 aof 文件"] FSyncPolicy -->|always| Disk FSyncPolicy -->|no| OSPage["交给 OS 自己 flush"] style Server fill:#f9d,stroke:#96c style Disk fill:#dfd,stroke:#6c6 ``` > [!TIP] 一条命令是怎么落到磁盘的? > 1. 客户端发送命令 → 2. Redis 在主线程执行并返回结果 → 3. 同时将命令追加到 `aof_buffer` → 4. 根据 `appendfsync` 策略最终刷入磁盘。整个过程对主线程几乎是异步的。 ### 刷新策略配置 ```conf # appendfsync always # 每次写入都 fsync -> 最安全,性能差 appendfsync everysec # 每秒 fsync <- 推荐(最佳平衡) # appendfsync no # 完全依赖 OS -> 性能最好,风险最高 ``` | 策略 | 丢失风险 | 吞吐量 (ops/s) | 适用场景 | |------|---------|---------------|---------| | `always` | 零丢失 | ~900 | 金融级要求(极少用) | | `everysec` | 最多丢 1 秒 | ~74k | **生产标准配置** | | `no` | OS 决定,可能丢大量 | ~93k | 可接受全量丢失的场景 | > [!TIP] everysec 的性能真相 > `everysec` 不会阻塞主线程——它把 fsync 放到单独的后台线程。即使某个 fsync 花了 2s(磁盘卡顿),也只是这一秒的写入被推迟到下一周期,不会影响主线程。 ## AOF 重写(Rewrite) AOF 文件会不断增长,需要定期重写来去除无效命令(如 key 被 DEL 后又 SET)。 ### 重写触发条件 ```conf auto-aof-rewrite-percentage 100 # AOF 比上次 rewrite 后增大的百分比 auto-aof-rewrite-min-size 64mb # AOF 最小绝对大小(避免刚启动就 rewrite) ``` 示例:当前 AOF = 128MB,上次 rewrite 后大小 = 64MB - 增量 = 128 - 64 = 64MB - 增量占比 = 64 / 64 = 100% -> 触发 rewrite - 但如果当前 AOF < 64MB -> 不触发 > [!QUESTION] 如果设置 auto-aof-rewrite-min-size 太大或太小会发生什么? > - **太大**:AOF 膨胀了很久才触发重写,磁盘空间浪费严重 > - **太小**:频繁重写,增加不必要的 CPU 和 IO 开销 > > 这本质上是一个「文件瘦身频率」的工程权衡题。 ### 重写过程(类似 RDB fork) ```mermaid sequenceDiagram participant S as Redis Server participant P as 主进程 child CG as Rewriter Child participant C as 重写子进程 participant Log as appendonly.aof participant NewLog as appendonly.aof_rewrite_tmp end S->>P: BGREWRITEAOF P->>S: OK P->>C: fork() 子进程 C->>NewLog: fork 时刻起遍历内存 -> 重写为精简命令 Note over P,C: Main process continues serving requests loop 每个新写命令 P->>Log: 追加原始命令 P->>C: 同时写入 aof_rewrite_buf
(重写期间的新操作) end C->>NewLog: 追加 aof_rewrite_buf 中的内容 C->>P: 完成 P->>Log: rename(原 -> 旧.bak, 新 -> 当前) ``` > [!WARNING] 重写的内存开销 > fork 后重写子进程也使用 COW 机制。如果你的 AOF 正在持续增长且写入量大,可能需要评估可用内存是否足够支撑 fork 的 COW 副本。在 100GB+ 的 Redis 实例上,一次 fork 可能导致额外的几 GB 内存占用。 ## AOF 开启方式 ```conf appendonly yes # 开启 AOF appendfilename "appendonly.aof" # AOF 文件名 dir /var/lib/redis # AOF/RDB 文件存放目录 ``` ```bash # Redis 7.x 目录结构(新版本使用多文件目录而非单文件) ls /var/lib/redis/appendonlydir/ total 256M drwxr-x--- 2 redis redis 4.0K May 15 18:00 . drwxr-x--- 2 redis redis 4.0K May 15 18:00 .. -rw-r----- 1 redis redis 16K May 15 18:00 manifest.aof -rw-r----- 1 redis redis 128M May 15 18:00 base.rdb -rw-r----- 1 redis redis 128M May 15 18:01 incr-0000000000000003.aof ``` ### 思考:为什么 AOF 会持续增长? 想象以下操作序列:`SET x 1 -> SET x 2 -> SET x 3 -> DEL x`。如果不做处理,AOF 中会记录这 4 条命令,但 key `x` 已经不存在了——这些写入了磁盘却没有任何价值。这就是为什么需要**重写**。 ## 混合持久化(Hybrid Persistence) 混合持久化是 Redis 4.0 引入、Redis 7 默认开启的特性——在 AOF 重写时,先用 RDB 快照全量备份当前内存状态,再用 AOF 追加增量变化。 ```conf # redis.conf aof-use-rdb-preamble yes # 开启混合持久化(默认值) ``` ```mermaid flowchart LR BG["BGREWRITEAOF"] --> Fork["fork 子进程"] Fork --> RDBPart["RDB 全量部分
二进制快照,写入极快"] Fork --> AOFPart["AOF 增量部分
仅含 fork 后的写操作"] RDBPart --> Merge["合并为临时文件"] AOFPart --> Merge Merge --> Rename["原子 rename 替换"] style RDBPart fill:#bbf,stroke:#66c style AOFPart fill:#dfd,stroke:#090 style Merge fill:#ffd,stroke:#cc0 ``` **好处有两个:** 1. **恢复速度快**:启动时先加载 RDB 全量快照(二进制格式比解析 AOF 命令快 10~100 倍),再回放少量 AOF 增量命令 2. **文件体积小**:相比纯 AOF 记录每条操作指令,混合模式下文件通常缩小 70%~90% > [!WARNING] 恢复流程的变化 > 混合模式下,Redis 启动时先读取 `base.rdb` 还原全部数据,再逐条执行增量 `.aof` 段中的命令。如果只保留 AOF 段而删除 `base.rdb`,恢复将退化为纯 AOF 模式,速度大幅下降。 ## AOF vs RDB 对比 | 维度 | AOF (`everysec`) | RDB(典型配置) | |------|-----------------|----------------| | 数据安全性 | ⭐⭐⭐ 最多丢 1s | ⭐ 可能丢数分钟 ~ 小时 | | 恢复速度 | ⭐⭐ 较慢(需逐条 replay) | ⭐⭐⭐ 快(二进制直接加载) | | CPU 开销 | ⭐⭐ 每秒一次 fsync | ⭐ 仅在 fork 时短暂增长 | | 磁盘 IO | ⭐⭐ 持续写入日志 | ⭐ 仅在快照间隔触发 | | 文件大小 | ⭐ 较大(逐条记录) | ⭐⭐⭐ 较小(二进制压缩) | | 数据丢失窗口 | <= 1 秒 | = save 间隔时间 | > [!QUESTION] 什么时候该用 RDB 而不是 AOF? > 如果你的业务场景是**缓存而非数据库**——数据可以重新构建或允许短暂丢失,那 RDB 就够了。只有当数据丢失会造成长期损失(如订单、余额)时,才需要上 AOF。 ## AOF 故障恢复 ```bash # 如果 AOF 损坏(断电等原因导致截断) redis-server --repair appendonly.aof # 或 Redis 7 下修复整个目录 redis-server --fix appendonlydir/aof-manifest ``` ### fsync 失败时的行为 ``` fsync -> EIO(磁盘错误) ↓ ERROR "I/O error writing to APPEND ONLY FILE..." ↓ 立即 SHUTDOWN NOSAVE(停止服务防止数据进一步损坏) ``` > [!NOTE] Redis 为什么会主动 shutdown? > 这是一种「fail-fast」设计哲学。AOF 的核心价值就是数据安全,当底层磁盘出现问题时,继续运行只会导致更多数据静默损坏。宁可停机也不能让用户在「不确定」的数据上继续业务。 > [!WARNING] AOF 不是万能的 > - AOF 修复工具只能恢复大部分,极端情况下会有部分命令丢失 > - 定期做冷备份(RDB 迁移到 OSS/S3)仍然是必须的兜底手段 ## AOF vs RDB 选型决策树 ```mermaid flowchart TD Start{"你的首要需求是?"} -->|"数据安全第一"| AOF["AOF everysec"] Start -->|"恢复速度第一"| RDB["RDB 仅"] Start -->|"两者兼顾"| HYBRID["混合持久化"] HYBRID --> Check{"数据重要性极高?"} Check -->|"是"| ALWAYS["AOF always
+ 每天冷备"] Check -->|"否"| EVERYSEC["AOF everysec
+ 混合持久化"] style HYBRID fill:#dfd,stroke:#090 style ALWAYS fill:#fdd,stroke:#900 style EVERYSEC fill:#dfd,stroke:#090 ``` > [!TIP] 生产环境黄金组合 > `AOF everysec + 混合持久化 + 定时 rdb 冷备到对象存储`——几乎覆盖所有常见场景。 ## 关联笔记 - [[hhs/Redis/04-RDB持久化]] — RDB 快照机制 - [[hhs/Redis/06-主从与哨兵]] — 哨兵监控中 AOF 状态检查 - [[hhs/Redis/09-运维调优]] — AOF 文件膨胀治理