This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/Redis/05-AOF持久化.md
T
2026-05-17 00:06:11 +08:00

219 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 主线程<br/>执行命令 + 返回结果"]
Server --> Buffer["aof_buffer<br/>内核/用户态缓冲区"]
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<br/>(重写期间的新操作)
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 全量部分<br/>二进制快照,写入极快"]
Fork --> AOFPart["AOF 增量部分<br/>仅含 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<br/>+ 每天冷备"]
Check -->|"否"| EVERYSEC["AOF everysec<br/>+ 混合持久化"]
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 文件膨胀治理