欢迎光临
我们一直在努力

Redis持久化-AOF原理

一、前言: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 QPS
    • everysec:≈ 5W ~ 10W QPS(接近无持久化)
    • no:≈ 10W+ QPS

    七、生产环境最佳实践

  • 必须开启混合持久化(aof-use-rdb-preamble yes)
  • 监控 AOF 重写频率:过于频繁说明写负载高或阈值过低
  • 避免大 Key 操作:如 HSET bigkey field1 v1 … field10000 v10000 会生成巨量 AOF
  • 使用 redis-check-aof 定期校验文件完整性
  • 将 AOF 目录挂载到独立高速磁盘

  • 八、结语

    感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

    赞(0)
    未经允许不得转载:171主机测评 » Redis持久化-AOF原理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址