2026-05-24 11:42:38 +08:00
|
|
|
|
---
|
|
|
|
|
|
tags: [Redis, 缓存, 持久化, AOF]
|
|
|
|
|
|
create time: 2026-05-15 18:13
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# AOF 持久化
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
AOF(Append Only File)以**追加写日志**的方式记录每个写操作。如果你用过 MySQL 的 binlog 或 Kafka 的 commit log,这个思想是一样的:**不存最终状态,而是存每一步变化过程**。
|
|
|
|
|
|
|
|
|
|
|
|
相比 [[hhs/Redis/04-RDB持久化|RDB]] 的快照式恢复,AOF 提供更高的数据安全性——你可以根据策略做到**不丢数据、丢 1 秒、或丢 N 条**。代价是文件更大、恢复更慢。
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] RDB 已经能持久化了,为什么还需要 AOF?
|
|
|
|
|
|
> RDB 是「拍照」——每隔 N 秒拍一张快照,两次拍照之间的数据变化全部丢失。AOF 是「录像」——每个写操作都被记录,最多只丢最后一次 fsync 之后的操作。打个比方:RDB 好比每小时存一次文档,AOF 好比开了自动保存。
|
|
|
|
|
|
|
|
|
|
|
|
## 核心机制
|
|
|
|
|
|
|
|
|
|
|
|
### 命令追加过程
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
Client["客户端写入"] --> Server["Redis 主线程<br/>执行命令 + 返回结果"]
|
|
|
|
|
|
Server --> Buffer["aof_buf<br/>用户态缓冲区"]
|
|
|
|
|
|
Buffer --> FSyncPolicy{"appendfsync 策略?"}
|
|
|
|
|
|
FSyncPolicy -->|everysec| KernelBuf["操作系统页缓存"]
|
|
|
|
|
|
KernelBuf --> Disk["磁盘 AOF 文件"]
|
|
|
|
|
|
FSyncPolicy -->|always| Disk
|
|
|
|
|
|
FSyncPolicy -->|no| OSPage["交给操作系统自行 flush"]
|
|
|
|
|
|
|
|
|
|
|
|
style Server fill:#f9d,stroke:#96c
|
|
|
|
|
|
style Disk fill:#dfd,stroke:#6c6
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 一条命令是怎么落到磁盘的?
|
|
|
|
|
|
> 1. 客户端发送 `SET mykey hello` → 2. Redis 主线程执行命令、更新内存、返回 OK → 3. **同时**将命令以 RESP 协议格式追加到 `aof_buf`(用户态缓冲区) → 4. 根据 `appendfsync` 策略调用 `fsync()` 最终刷入磁盘
|
|
|
|
|
|
>
|
|
|
|
|
|
> 注意步骤 2 和 3 是同步的,但步骤 4 是异步的——这意味着**命令返回成功 ≠ 数据落盘**。
|
|
|
|
|
|
|
|
|
|
|
|
实际落盘的 AOF 文件长这样(RESP 协议格式):
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nhello\r\n
|
|
|
|
|
|
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nworld\r\n
|
|
|
|
|
|
*2\r\n$3\r\nDEL\r\n$5\r\nmykey\r\n
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 为什么 AOF 用 RESP 协议而不是人类可读的纯文本?
|
|
|
|
|
|
> RESP 是 Redis 自身的通信协议,选择它的原因很简单:**不需要额外的序列化/反序列化开销**,Redis 直接把发给客户端的响应原样写入 AOF,读取时也直接用 RESP 解析器回放。这比发明一套新格式要高效得多。
|
|
|
|
|
|
|
|
|
|
|
|
### 刷新策略配置
|
|
|
|
|
|
|
|
|
|
|
|
```conf
|
|
|
|
|
|
# appendfsync always # 每次写入都 fsync -> 最安全,性能差
|
|
|
|
|
|
appendfsync everysec # 每秒 fsync <- 推荐(最佳平衡)
|
|
|
|
|
|
# appendfsync no # 完全依赖 OS -> 性能最好,风险最高
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
在理解这三种策略之前,先搞清楚 `fsync` 到底在做什么:
|
|
|
|
|
|
|
|
|
|
|
|
> [!info] fsync 的本质:把"快递柜"里的包裹真正送到你手里
|
|
|
|
|
|
> 当 Redis 调用 `write()` 时,数据只是从用户态拷贝到了**内核页缓存**(类似放进了快递柜),操作系统会择机将其刷到物理磁盘。而 `fsync()` 是强制要求操作系统**立即**把页缓存的数据持久化到磁盘——相当于要求快递柜立即派送。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 如果不调用 `fsync()`,数据在页缓存中时如果发生断电,这些数据就丢了。
|
|
|
|
|
|
|
|
|
|
|
|
理解了 fsync,三种策略的区别就一目了然:
|
|
|
|
|
|
|
|
|
|
|
|
| 策略 | fsync 时机 | 丢失风险 | 吞吐量 (ops/s) | 适用场景 |
|
|
|
|
|
|
|------|-----------|---------|---------------|---------|
|
2026-05-25 23:07:45 +08:00
|
|
|
|
| `always` | 每条写命令后 | **零丢失** | ~900(注) | 金融级要求(极少用) |
|
2026-05-24 11:42:38 +08:00
|
|
|
|
| `everysec` | 每秒一次(后台线程) | 最多丢 ~1 秒 | ~74k | **生产标准配置** |
|
|
|
|
|
|
| `no` | 从不主动 fsync,交给 OS | OS 决定,可能丢 30 秒+ | ~93k | 可接受数据丢失的场景 |
|
|
|
|
|
|
|
2026-05-25 23:07:45 +08:00
|
|
|
|
> [!note] 吞吐量数据来源
|
|
|
|
|
|
> 以上数据来自 Redis 官方基准测试(`redis-benchmark`,HDD 磁盘)。实际生产环境中,`always` 在 SSD 上可达数万 ops/s,`everysec` 可达十万级。**具体性能以实际压测为准**,这里主要体现三者之间的相对差距。
|
|
|
|
|
|
|
2026-05-24 11:42:38 +08:00
|
|
|
|
> [!warning] everysec "最多丢 1 秒" 是有前提条件的
|
2026-05-25 23:07:45 +08:00
|
|
|
|
> 这个说法成立的前提是 `fsync()` 能在 1 秒内完成。如果磁盘 IO 阻塞(比如同一块盘上跑着其他 IO 密集任务),一次 `fsync()` 可能卡住几秒甚至十几秒——此时未 fsync 的数据量会持续累积,丢失窗口也随之扩大。
|
|
|
|
|
|
>
|
|
|
|
|
|
> 补充一个实现细节:`everysec` 默认在**主线程**执行 `fsync()`,只有当 Redis 检测到上次 `fsync()` 耗时超过 2 秒时,才会自动降级到**后台线程**执行——这样避免长时间 fsync 阻塞主线程,但代价是主线程在降级期间的写入数据安全性进一步降低。
|
2026-05-24 11:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
> [!tip] 为什么 always 这么慢?
|
|
|
|
|
|
> `always` 是在**主线程**中同步调用 `fsync()`。一次 `fsync()` 耗时约 0.2~2ms(取决于磁盘),这意味着每条写命令都要等磁盘确认,吞吐量直接从万级跌到百级——这就是"数据安全 vs 性能"的经典权衡。
|
|
|
|
|
|
|
|
|
|
|
|
## AOF 重写(Rewrite)
|
|
|
|
|
|
|
|
|
|
|
|
AOF 文件只会**追加**、永远不会自动清理。时间一长,大量"垃圾命令"会让文件体积失控:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
subgraph AOF_LOG["appendonly.aof(实际内容)"]
|
|
|
|
|
|
CMD1["SET counter 0"]
|
|
|
|
|
|
CMD2["SET counter 1"]
|
|
|
|
|
|
CMD3["SET counter 2"]
|
|
|
|
|
|
CMD4["INCR counter"]
|
|
|
|
|
|
CMD5["DEL temp_key"]
|
|
|
|
|
|
CMD6["SET temp_key abc"]
|
|
|
|
|
|
CMD7["DEL temp_key"]
|
|
|
|
|
|
end
|
|
|
|
|
|
subgraph REALITY["内存中的最终状态"]
|
|
|
|
|
|
MEM["counter = 3<br/>temp_key 不存在"]
|
|
|
|
|
|
end
|
|
|
|
|
|
AOF_LOG -.->|"7 条命令 → 真实状态只有 1 条"| REALITY
|
|
|
|
|
|
|
|
|
|
|
|
style AOF_LOG fill:#fdd,stroke:#c66
|
|
|
|
|
|
style REALITY fill:#dfd,stroke:#090
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 为什么不直接"删除"过期命令?
|
|
|
|
|
|
> AOF 是顺序追加的日志文件,就像流水账本——你不能从账本中间撕掉一页而不影响后面所有页码。**只能"重写":把当前内存的真实状态重新写一份新的、精简的 AOF 文件来替换旧文件。**
|
|
|
|
|
|
|
|
|
|
|
|
### 重写触发条件
|
|
|
|
|
|
|
|
|
|
|
|
```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 开销
|
|
|
|
|
|
>
|
|
|
|
|
|
> 这本质上是一个「文件瘦身频率」的工程权衡题。
|
|
|
|
|
|
|
2026-05-25 23:07:45 +08:00
|
|
|
|
### 手动触发重写
|
|
|
|
|
|
|
|
|
|
|
|
除了自动触发,运维中经常需要手动重写——比如刚做了一次大批量数据导入,AOF 文件暴涨。
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 手动触发 AOF 重写(后台异步执行,不阻塞主线程)
|
|
|
|
|
|
BGREWRITEAOF
|
|
|
|
|
|
|
|
|
|
|
|
# 查看重写是否正在进行
|
|
|
|
|
|
INFO persistence
|
|
|
|
|
|
# aof_rewrite_in_progress: 1 ← 正在重写中
|
|
|
|
|
|
# aof_rewrite_scheduled: 0 ← 是否有排队中的重写请求
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!warning] 重写互斥
|
|
|
|
|
|
> 和 BGSAVE 一样,Redis 同一时刻**只允许一个子进程**做持久化。如果 BGSAVE 正在执行,`BGREWRITEAOF` 会排队等待(`aof_rewrite_scheduled: 1`),等 BGSAVE 完成后自动触发。
|
|
|
|
|
|
|
2026-05-24 11:42:38 +08:00
|
|
|
|
### 重写过程(类似 RDB fork)
|
|
|
|
|
|
|
|
|
|
|
|
重写并不是简单地"删减旧 AOF",而是**从内存中的真实数据出发,生成全新的精简 AOF 文件**。整个过程分三步:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant P as "主进程(继续服务)"
|
|
|
|
|
|
participant C as "重写子进程"
|
|
|
|
|
|
participant Old as "appendonly.aof(旧)"
|
|
|
|
|
|
participant Tmp as "aof_rewrite_tmp(新)"
|
|
|
|
|
|
participant Buf as "aof_rewrite_buf"
|
|
|
|
|
|
|
|
|
|
|
|
P->>P: "收到 BGREWRITEAOF 命令"
|
|
|
|
|
|
P->>C: "fork() 创建子进程"
|
|
|
|
|
|
Note over C: "第一步:遍历内存所有 key<br/>生成精简命令写入新文件"
|
|
|
|
|
|
C->>Tmp: "SET key1 val1<br/>SET key2 val2<br/>HSET hash f1 v1 ..."
|
|
|
|
|
|
Note over P: "主进程继续正常处理请求"
|
|
|
|
|
|
P->>Old: "新命令照常追加到旧 AOF"
|
|
|
|
|
|
P->>Buf: "同时复制一份到重写缓冲区"
|
|
|
|
|
|
Note over C: "第二步:内存遍历完成"
|
|
|
|
|
|
C->>Buf: "请求主进程发送缓冲区内容"
|
|
|
|
|
|
Note over C: "第三步:把重写期间的新命令追加到新文件末尾"
|
|
|
|
|
|
Buf->>Tmp: "追加增量命令"
|
|
|
|
|
|
C->>P: "重写完成通知主进程"
|
|
|
|
|
|
P->>P: "原子 rename:旧文件 → .bak,新文件 → appendonly.aof"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 关键理解:为什么需要"重写缓冲区"?
|
|
|
|
|
|
> 子进程在遍历内存生成新 AOF 的过程中(可能持续几十秒甚至几分钟),主进程还在不断接收新写命令。这些"重写期间的增量命令"被暂存在 `aof_rewrite_buf` 中,等子进程遍历完内存后,再将这些增量追加到新文件末尾——这样才能保证新 AOF 不丢任何数据。
|
|
|
|
|
|
|
|
|
|
|
|
> [!warning] 重写的内存开销
|
|
|
|
|
|
> fork 后重写子进程使用 COW(Copy-On-Write)机制,和 RDB 的 BGSAVE 一样。如果你的实例内存很大(100GB+)且写入频繁,fork 可能导致额外几 GB 的内存占用。在内存紧张的环境中,建议控制 AOF 文件大小、及时触发重写,避免单次重写数据量过大。
|
|
|
|
|
|
|
|
|
|
|
|
## AOF 开启方式
|
|
|
|
|
|
|
|
|
|
|
|
```conf
|
|
|
|
|
|
appendonly yes # 开启 AOF(默认 no)
|
|
|
|
|
|
appendfilename "appendonly.aof" # AOF 文件名(Redis 7 会在此基础上创建子目录)
|
|
|
|
|
|
dir /var/lib/redis # AOF/RDB 文件存放目录
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 在线开启/关闭 AOF
|
|
|
|
|
|
> 可以通过 `CONFIG SET appendonly yes` 在运行时开启 AOF,但需要注意:这会触发一次阻塞式的 AOF 初始化(类似 BGSAVE),**生产环境建议在低峰期操作**。关闭 AOF 同理:`CONFIG SET appendonly no`。
|
|
|
|
|
|
|
|
|
|
|
|
Redis 7.x 的多文件目录结构:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
ls /var/lib/redis/appendonlydir/
|
|
|
|
|
|
# total 256M
|
2026-05-25 23:07:45 +08:00
|
|
|
|
# appendonly.aof.manifest <- 文件清单(记录哪些文件组成完整 AOF)
|
2026-05-24 11:42:38 +08:00
|
|
|
|
# base.rdb <- 最近一次重写生成的 RDB 全量快照
|
|
|
|
|
|
# incr-0000000000000003.aof <- 增量 AOF(记录重写之后的新写操作)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 为什么 Redis 7 要把单文件拆成目录结构?
|
|
|
|
|
|
> 旧版单个 AOF 文件有一个痛点:重写时需要先生成临时文件,再 `rename` 替换。如果 AOF 有 50GB,rename 操作虽然原子但磁盘 IO 开销巨大。拆成多文件后,重写只需替换 `base.rdb` 和新增 `incr-*.aof`,IO 开销大幅降低,也更方便做增量备份。
|
|
|
|
|
|
|
2026-05-25 23:07:45 +08:00
|
|
|
|
### AOF 关键配置参数汇总
|
|
|
|
|
|
|
|
|
|
|
|
除了前面讲过的 `appendfsync`,还有几个配置项直接影响 AOF 的行为和稳定性:
|
|
|
|
|
|
|
|
|
|
|
|
```conf
|
|
|
|
|
|
# ---- AOF 核心参数 ----
|
|
|
|
|
|
appendonly yes # 开启 AOF
|
|
|
|
|
|
appendfilename "appendonly.aof" # AOF 文件名
|
|
|
|
|
|
appendfsync everysec # 刷新策略(always / everysec / no)
|
|
|
|
|
|
|
|
|
|
|
|
# ---- 重写相关 ----
|
|
|
|
|
|
auto-aof-rewrite-percentage 100 # AOF 增长百分比触发重写
|
|
|
|
|
|
auto-aof-rewrite-min-size 64mb # AOF 最小绝对大小才触发重写
|
|
|
|
|
|
no-appendfsync-on-rewrite no # 重写期间是否暂停 fsync(见下方说明)
|
|
|
|
|
|
|
|
|
|
|
|
# ---- 容错相关 ----
|
|
|
|
|
|
aof-load-truncated yes # 启动加载时是否允许截断末尾损坏的命令
|
|
|
|
|
|
aof-use-rdb-preamble yes # 重写时是否使用 RDB 作为前缀(混合持久化开关)
|
|
|
|
|
|
|
|
|
|
|
|
# ---- Redis 7 新增 ----
|
|
|
|
|
|
aof-timestamp-enable no # 是否在 AOF 中记录时间戳(用于 PITR)
|
|
|
|
|
|
aof-ignore-errors no # fsync 失败时是否忽略错误继续运行
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| 参数 | 推荐值 | 要点 |
|
|
|
|
|
|
|------|--------|------|
|
|
|
|
|
|
| `no-appendfsync-on-rewrite` | `no` | 设为 `yes` 时,AOF 重写期间暂停 fsync 以减少磁盘争抢,但代价是**重写期间的数据安全性降级为 `no` 策略**——只在磁盘 IO 严重瓶颈时考虑 |
|
|
|
|
|
|
| `aof-load-truncated` | `yes` | 设为 `yes` 时,Redis 启动发现 AOF 末尾不完整会自动丢弃末尾损坏命令并继续加载。设为 `no` 则直接报错退出——更安全但需要人工介入修复 |
|
|
|
|
|
|
| `aof-timestamp-enable` | 按需 | Redis 7 的实验性功能,开启后 AOF 中会嵌入时间戳注释,配合 `redis-check-aof --timestamp` 可实现**按时间点恢复(PITR)**——比如"恢复到今天 14:30 的状态"。目前仍是实验特性,生产环境谨慎使用 |
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] `no-appendfsync-on-rewrite` 什么时候该开?
|
|
|
|
|
|
> 当你观察到 AOF 重写期间主线程出现明显延迟(`redis-cli --latency` 抖动),且 `INFO persistence` 显示 `aof_delayed_fsync` 持续增长时,可以考虑临时开启。这相当于用**重写期间的数据安全换性能**,是一把双刃剑。
|
|
|
|
|
|
|
2026-05-24 11:42:38 +08:00
|
|
|
|
## 混合持久化(Hybrid Persistence)
|
|
|
|
|
|
|
2026-05-24 21:18:14 +08:00
|
|
|
|
> [!tip] 混合持久化是 AOF 重写的"终极形态"
|
2026-05-25 23:07:45 +08:00
|
|
|
|
> Redis 4.0 引入、Redis 5.0 开始默认开启(`aof-use-rdb-preamble yes`)。核心思路:AOF **重写**时先写 RDB 全量快照作为"底子",再追加增量 AOF 命令作为"补丁"——恢复时先加载 RDB(秒级),再回放少量增量 AOF(补齐数据)。
|
2026-05-24 11:42:38 +08:00
|
|
|
|
>
|
2026-05-24 21:18:14 +08:00
|
|
|
|
> **在 AOF 视角下**:重写后生成的文件不再全是文本命令,而是 `RDB 二进制基线 + AOF 增量日志` 的混合结构。好处是恢复速度快(接近纯 RDB)且数据安全性高(接近纯 AOF)。
|
2026-05-24 11:42:38 +08:00
|
|
|
|
>
|
2026-05-24 21:18:14 +08:00
|
|
|
|
> 详细的文件结构、原理图解和配置方式,参见 [[hhs/Redis/04-RDB持久化#Redis 混合持久化(推荐)|混合持久化完整章节]]。
|
2026-05-24 11:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
## AOF vs RDB 对比
|
|
|
|
|
|
|
|
|
|
|
|
| 维度 | AOF (`everysec`) | RDB(典型配置) |
|
|
|
|
|
|
|------|-----------------|----------------|
|
|
|
|
|
|
| 数据安全性 | ⭐⭐⭐ 最多丢 1s | ⭐ 可能丢数分钟 ~ 小时 |
|
|
|
|
|
|
| 恢复速度 | ⭐⭐ 较慢(需逐条 replay) | ⭐⭐⭐ 快(二进制直接加载) |
|
|
|
|
|
|
| CPU 开销 | ⭐⭐ 每秒一次 fsync | ⭐ 仅在 fork 时短暂增长 |
|
|
|
|
|
|
| 磁盘 IO | ⭐⭐ 持续写入日志 | ⭐ 仅在快照间隔触发 |
|
|
|
|
|
|
| 文件大小 | ⭐ 较大(逐条记录) | ⭐⭐⭐ 较小(二进制压缩) |
|
|
|
|
|
|
| 数据丢失窗口 | <= 1 秒 | = save 间隔时间 |
|
|
|
|
|
|
|
|
|
|
|
|
> [!QUESTION] 什么时候该用 RDB 而不是 AOF?
|
|
|
|
|
|
> 如果你的业务场景是**缓存而非数据库**——数据可以重新构建或允许短暂丢失,那 RDB 就够了。只有当数据丢失会造成长期损失(如订单、余额)时,才需要上 AOF。
|
|
|
|
|
|
|
|
|
|
|
|
## AOF 故障恢复
|
|
|
|
|
|
|
|
|
|
|
|
断电或异常退出时,AOF 文件可能出现**尾部截断**(最后一条命令不完整)。Redis 启动时会自动检测并尝试修复。
|
|
|
|
|
|
|
|
|
|
|
|
### 常见故障场景与修复
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 场景一:AOF 文件末尾被截断(最常见——断电导致最后一条命令写了一半)
|
|
|
|
|
|
# Redis 启动时会自动丢弃末尾不完整的命令,无需手动干预
|
|
|
|
|
|
|
|
|
|
|
|
# 场景二:AOF 文件损坏严重(如磁盘坏块导致文件中间损坏)
|
|
|
|
|
|
# 需要手动修复:找到最后一个合法命令,截断之后的内容
|
|
|
|
|
|
redis-check-aof --fix appendonly.aof
|
|
|
|
|
|
|
|
|
|
|
|
# 场景三:Redis 7 多文件目录结构
|
2026-05-25 23:07:45 +08:00
|
|
|
|
# 先查看 manifest 了解文件组成
|
|
|
|
|
|
cat /var/lib/redis/appendonlydir/appendonly.aof.manifest
|
|
|
|
|
|
# 修复具体的增量文件(找到损坏的那个)
|
|
|
|
|
|
redis-check-aof --fix /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof
|
|
|
|
|
|
# 注意:base.rdb 损坏需要用 redis-check-rdb 修复
|
2026-05-24 11:42:38 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 实际修复流程
|
|
|
|
|
|
> 1. 先备份损坏的 AOF 文件:`cp appendonly.aof appendonly.aof.bak`
|
|
|
|
|
|
> 2. 运行 `redis-check-aof --fix`(交互式,会告诉你损坏位置并确认是否截断)
|
|
|
|
|
|
> 3. 重新启动 Redis,检查数据完整性
|
|
|
|
|
|
> 4. 如果数据缺失严重,从 RDB 冷备份恢复 + 回放最近的增量 AOF
|
|
|
|
|
|
|
|
|
|
|
|
### fsync 失败时的行为
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
FS["fsync() 调用"] --> OK{"返回成功?"}
|
|
|
|
|
|
OK -->|"是"| CONT["继续正常服务"]
|
|
|
|
|
|
OK -->|"否:EIO 磁盘错误"| LOG["记录日志<br/>I/O error writing to APPEND ONLY FILE"]
|
2026-05-25 23:07:45 +08:00
|
|
|
|
LOG --> CFG{"aof-ignore-errors<br/>配置?"}
|
|
|
|
|
|
CFG -->|"no (默认)"| SD["执行 SHUTDOWN NOSAVE<br/>立即停止服务"]
|
|
|
|
|
|
CFG -->|"yes (Redis 7+)"| IGN["忽略错误,继续服务<br/>但数据可能丢失"]
|
2026-05-24 11:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
style SD fill:#fdd,stroke:#c00
|
2026-05-25 23:07:45 +08:00
|
|
|
|
style IGN fill:#ffd,stroke:#c90
|
2026-05-24 11:42:38 +08:00
|
|
|
|
style CONT fill:#dfd,stroke:#090
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!note] Redis 为什么会主动 shutdown?
|
2026-05-25 23:07:45 +08:00
|
|
|
|
> 默认行为是一种「fail-fast」设计哲学。AOF 的核心价值就是数据安全,当底层磁盘出现问题时,继续运行只会导致更多数据静默损坏。**宁可停机也不能让用户在"不确定"的数据上继续业务**——这和数据库遇到 WAL 写入失败时的处理逻辑是一样的。
|
|
|
|
|
|
>
|
|
|
|
|
|
> Redis 7 引入了 `aof-ignore-errors` 配置项,设为 `yes` 时 Redis 会忽略 AOF 写入错误继续运行。但除非你完全理解后果(数据静默丢失),否则**不建议开启**。
|
2026-05-24 11:42:38 +08:00
|
|
|
|
|
|
|
|
|
|
> [!warning] AOF 不是万能的
|
|
|
|
|
|
> - AOF 修复工具只能处理末尾截断,中间损坏只能截断丢失后面的数据
|
|
|
|
|
|
> - 定期做冷备份(RDB 快照传到 OSS/S3)仍然是必须的兜底手段
|
|
|
|
|
|
> - 生产环境建议部署**主从架构**:即使主节点 AOF 出问题,从节点仍有完整数据
|
|
|
|
|
|
|
|
|
|
|
|
## 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 冷备到对象存储`——几乎覆盖所有常见场景。
|
|
|
|
|
|
|
2026-05-25 23:07:45 +08:00
|
|
|
|
## 运维速查手册
|
|
|
|
|
|
|
|
|
|
|
|
### AOF 监控关键指标
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
INFO persistence
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
重点关注以下字段:
|
|
|
|
|
|
|
|
|
|
|
|
| 指标 | 含义 | 告警建议 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| `aof_enabled` | AOF 是否开启 | 应为 `1` |
|
|
|
|
|
|
| `aof_rewrite_in_progress` | 是否正在重写 | 长时间为 `1` 需排查 |
|
|
|
|
|
|
| `aof_rewrite_scheduled` | 是否有排队中的重写 | 长时间为 `1` 说明重写被阻塞 |
|
|
|
|
|
|
| `aof_last_bgrewrite_status` | 上次重写是否成功 | ≠ `ok` 立即告警 |
|
|
|
|
|
|
| `aof_last_write_status` | 上次 AOF 写入是否成功 | ≠ `ok` 说明磁盘可能有问题 |
|
|
|
|
|
|
| `aof_current_size` | 当前 AOF 文件大小 | 持续膨胀未触发重写需检查配置 |
|
|
|
|
|
|
| `aof_base_size` | 上次重写后 AOF 大小 | 结合 `aof_current_size` 计算增长比 |
|
|
|
|
|
|
| `aof_delayed_fsync` | 被延迟的 fsync 次数 | 持续增长说明磁盘 IO 成为瓶颈 |
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] 如何快速判断 AOF 是否健康?
|
|
|
|
|
|
> 一个简单公式:`aof_current_size / aof_base_size`。如果比值远大于 `auto-aof-rewrite-percentage` 的配置值(默认 2.0),说明自动重写可能没有正常触发——检查 `auto-aof-rewrite-min-size` 和日志。
|
|
|
|
|
|
|
|
|
|
|
|
### 常用运维命令
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# ---- 手动触发 AOF 重写 ----
|
|
|
|
|
|
BGREWRITEAOF
|
|
|
|
|
|
|
|
|
|
|
|
# ---- 查看 AOF 文件大小 ----
|
|
|
|
|
|
ls -lh /var/lib/redis/appendonlydir/
|
|
|
|
|
|
# Redis 7: 查看各组件文件
|
|
|
|
|
|
ls -lh /var/lib/redis/appendonlydir/base.rdb
|
|
|
|
|
|
ls -lh /var/lib/redis/appendonlydir/incr-*.aof
|
|
|
|
|
|
|
|
|
|
|
|
# ---- 查看 AOF 文件内容(前 10 条命令,RESP 格式) ----
|
|
|
|
|
|
head -20 /var/lib/redis/appendonlydir/incr-0000000000000003.aof
|
|
|
|
|
|
|
|
|
|
|
|
# ---- 运行时动态调整 fsync 策略 ----
|
|
|
|
|
|
redis-cli CONFIG SET appendfsync always # 临时切到最安全模式(如做批量导入前)
|
|
|
|
|
|
redis-cli CONFIG SET appendfsync everysec # 恢复推荐配置
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!tip] 运维实战技巧
|
|
|
|
|
|
> **大批量数据导入时**:临时切到 `appendfsync no` 或 `appendfsync everysec`,导入完成后再切回 `always`(如果需要)。避免每条写命令都 fsync 导致导入耗时翻数十倍。
|
|
|
|
|
|
>
|
|
|
|
|
|
> **磁盘空间告急时**:手动触发 `BGREWRITEAOF` 可以立即压缩 AOF 文件体积,通常能减少 50%-80% 的空间占用。
|
|
|
|
|
|
|
2026-05-24 11:42:38 +08:00
|
|
|
|
## 关联笔记
|
|
|
|
|
|
|
|
|
|
|
|
- [[hhs/Redis/04-RDB持久化]] — RDB 快照机制
|
|
|
|
|
|
- [[hhs/Redis/06-主从与哨兵]] — 哨兵监控中 AOF 状态检查
|
2026-05-24 21:18:14 +08:00
|
|
|
|
- [[hhs/Redis/11-运维与性能调优]] — AOF 文件膨胀治理
|