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 恢复流程

  1. 识别文件开头是 RDB 格式
  2. 直接加载 RDB 部分(秒级)
  3. 重放后续 AOF 命令(仅需处理增量)

✅ 优势兼顾 RDB 的速度 + AOF 的安全性


六、AOF 的可靠性与性能权衡

场景推荐配置说明
金融/支付系统appendfsync always宁可慢,不能丢
电商/社交 Appappendfsync everysec + 混合持久化黄金组合
日志/缓存场景appendfsync no 或仅 RDB性能优先

📊 性能实测参考(SSD 环境):

  • always:≈ 500 ~ 1000 QPS
  • everysec:≈ 5W ~ 10W QPS(接近无持久化)
  • no:≈ 10W+ QPS

七、生产环境最佳实践

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

八、结语

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值