219 lines
8.6 KiB
Markdown
219 lines
8.6 KiB
Markdown
---
|
||
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 文件膨胀治理
|