一、前言:AOF 不是简单的“日志追加”
很多开发者认为 AOF 就是“把命令写到文件里”,
但真正的挑战在于:
- 如何在高并发写入下保证日志一致性?
- 如何避免 AOF 文件无限膨胀?
- 如何在重写期间不丢失新写入的数据?
- 如何平衡数据安全与性能损耗?
本文将带你深入 Redis 源码级实现,揭开 AOF 背后的精妙设计。
二、AOF 的核心目标与三大难题
目标
记录所有修改数据库状态的写命令,使得 Redis 重启后可通过重放这些命令恢复原始数据集。
三大难题
| 难题 | Redis 的解决方案 |
|---|---|
| 1. 写入性能 vs 数据安全 | 提供 appendfsync 三级策略(always/everysec/no) |
| 2. AOF 文件无限增长 | 引入 AOF 重写(Rewrite) 机制 |
| 3. 重写期间数据一致性 | 使用 双缓冲区 + 子进程 COW |
三、AOF 工作全流程(含源码逻辑)
3.1 命令追加:从执行到缓冲
当一个写命令(如 SET key value)被执行时:
// server.c: call()
void call(client *c, int flags) {
// ... 执行命令 ...
if (flags & CMD_CALL_PROPAGATE && ...) {
propagate(cmd, c->db->id, c->argv, c->argc, PROPAGATE_AOF);
}
}
// aof.c: feedAppendOnlyFile()
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
// 1. 切换 DB(如果需要)
if (server.aof_selected_db != dictid) {
snprintf(buf, 32, "*2\r\n$6\r\nSELECT\r\n$%ld\r\n%d\r\n", ...);
bufptr = buf;
server.aof_selected_db = dictid;
}
// 2. 将命令转换为 RESP 格式
cmdrepr = catAppendOnlyGenericCommand(buf, argc, argv);
// 3. 追加到 AOF 缓冲区(aof_buf)
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf, cmdrepr, sdslen(cmdrepr));
}
✅ 关键点:
- 所有写命令最终都进入
server.aof_buf(sds 字符串)- 读命令(如 GET)不会被记录
3.2 文件写入与同步:fsync 的三种策略
在每次事件循环结束前(beforeSleep()),Redis 会处理 AOF 缓冲区:
// aof.c: flushAppendOnlyFile()
void flushAppendOnlyFile(int force) {
if (sdslen(server.aof_buf) == 0) return;
// 写入内核缓冲区(write)
nwritten = aofWrite(server.aof_fd, server.aof_buf, sdslen(server.aof_buf));
// 根据策略决定是否 fsync
if (server.appendfsync == APPENDFSYNC_ALWAYS) {
aof_fsync(server.aof_fd); // 立即刷盘
server.aof_last_fsync = server.unixtime;
} else if (server.appendfsync == APPENDFSYNC_EVERYSEC) {
if (server.unixtime > server.afo_last_fsync) {
// 创建后台线程执行 fsync(避免阻塞主线程)
bioCreateBackgroundJob(BIO_AOF_FSYNC, (void*)server.aof_fd, NULL, NULL);
server.aof_last_fsync = server.unixtime;
}
}
}
🔑 性能关键:
always:每次fsync→ 强一致,但性能极差everysec:后台线程 fsync → 主线程不阻塞,最多丢 1 秒数据no:依赖 OS → 性能最好,但风险最高
四、AOF 重写(Rewrite):解决膨胀的核心机制
4.1 为什么需要重写?
假设执行:
INCR counter # 100 万次
AOF 会记录 100 万条 INCR 命令,但实际只需一条:
SET counter 1000000
4.2 重写不是“压缩旧文件”,而是“重建新文件”!
重要误区纠正:
AOF 重写不会读取原 AOF 文件,而是直接遍历当前内存中的数据库,生成最小命令集。
4.3 重写全流程(BGREWRITEAOF)
✅ 关键设计:
- 双缓冲区:确保重写期间的新命令不丢失
- 原子替换:通过
rename()保证文件一致性- COW 共享内存:子进程无需复制全量数据
4.4 重写触发条件(自动)
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
触发公式:
当前 AOF 大小 > (上次重写后大小 × (1 + percentage/100))
且
当前 AOF 大小 > min-size
例如:
- 上次重写后 AOF 为 50MB
- 当前 AOF 达到 100MB(50 × 2)→ 触发重写
五、混合持久化(RDB + AOF):Redis 4.0+ 的终极方案
5.1 问题:纯 AOF 恢复太慢!
- 10GB AOF 文件需重放数百万条命令 → 启动耗时分钟级
5.2 解决方案:AOF 文件 = RDB 快照 + AOF 增量
启用配置:
aof-use-rdb-preamble yes
文件结构:
[REDIS][rdb_version][RDB DATA...][EOF][AOF INCREMENTAL COMMANDS...]
5.3 恢复流程
- 识别文件开头是 RDB 格式
- 直接加载 RDB 部分(秒级)
- 重放后续 AOF 命令(仅需处理增量)
✅ 优势:兼顾 RDB 的速度 + AOF 的安全性
六、AOF 的可靠性与性能权衡
| 场景 | 推荐配置 | 说明 |
|---|---|---|
| 金融/支付系统 | appendfsync always | 宁可慢,不能丢 |
| 电商/社交 App | appendfsync everysec + 混合持久化 | 黄金组合 |
| 日志/缓存场景 | appendfsync no 或仅 RDB | 性能优先 |
📊 性能实测参考(SSD 环境):
always:≈ 500 ~ 1000 QPSeverysec:≈ 5W ~ 10W QPS(接近无持久化)no:≈ 10W+ QPS
七、生产环境最佳实践
- 必须开启混合持久化(
aof-use-rdb-preamble yes) - 监控 AOF 重写频率:过于频繁说明写负载高或阈值过低
- 避免大 Key 操作:如
HSET bigkey field1 v1 ... field10000 v10000会生成巨量 AOF - 使用
redis-check-aof定期校验文件完整性 - 将 AOF 目录挂载到独立高速磁盘
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

403

被折叠的 条评论
为什么被折叠?



