redis中AOF 重写机制解析
AOFAppend Only File持久化机制通过记录所有写命令来保证数据安全但随之而来的问题是随着运行时间增长AOF 文件会不断膨胀。假设你反复对一个 key 执行INCR操作 1000 次AOF 文件中会记录 1000 条INCR命令但恢复时只需要一条SET key 1000即可。这就是 AOF 重写的价值所在。AOF 重写的核心目标就是将 AOF 文件重写为包含相同数据集的最小命令集从而减少文件体积加速数据恢复。一、问题背景AOF 文件的膨胀1.1 AOF 持久化回顾AOF 机制以日志形式记录每个写命令。当执行以下操作时127.0.0.1:6379 SET user:1000:name Alice OK 127.0.0.1:6379 SET user:1000:name Bob OK 127.0.0.1:6379 SET user:1000:name Charlie OKAOF 文件会记录三条命令。但实际上只有最后一条SET user:1000:name Charlie是恢复数据所需要的。1.2 膨胀带来的问题问题影响磁盘空间浪费大量冗余命令占用存储数据恢复缓慢启动时需回放所有历史命令主从复制压力传输大文件占用网络带宽二、核心设计思想AOF 重写与直接处理旧 AOF 文件不同它采用了一种巧妙的方式不是“重写旧文件”而是“基于当前内存数据生成新文件”。这种方式相当于定期对数据库做一个“快照”用一系列能够重建当前数据状态的命令替换旧的、冗长的命令序列。2.1 核心原则最小命令集原则只生成重建当前数据集所需的最少命令。后台执行原则通过子进程完成不阻塞主进程。原子替换原则新旧文件切换是原子的不会丢失数据。三、重写工作的三阶段3.1 准备阶段触发与状态检查在redis中无论是手动触发还是自动触发rewriteAppendOnlyFileBackground()函数是入口// 伪代码AOF重写入口 int rewriteAppendOnlyFileBackground() { // 1. 检查是否已有重写进程在运行 if (server.aof_rewrite_child_pid ! -1) { return C_ERR; // 已有重写进程忽略本次请求 } // 2. 检查是否正在执行 RDB 持久化 if (server.rdb_child_pid ! -1) { // 等待 RDB 完成后再执行 server.aof_rewrite_scheduled 1; return C_OK; } // 3. 执行 fork 创建子进程 pid_t child_pid fork(); if (child_pid 0) { // 子进程执行重写 rewriteAppendOnlyFile(); exit(0); } else { // 父进程记录子进程 PID初始化重写缓冲区 server.aof_rewrite_child_pid child_pid; server.aof_rewrite_buf sdsempty(); // 创建重写缓冲区 return C_OK; } }3.2 执行阶段子进程的核心工作这是重写的核心工作子进程遍历整个数据库生成新的 AOF 文件// 伪代码子进程重写 AOF 文件 void rewriteAppendOnlyFile() { // 1. 创建临时文件 FILE* fp createTempFile(temp-rewrite.aof); // 2. 写入 SELECT 命令选择数据库 // Redis 默认有 16 个数据库需要确保恢复时切换到正确的数据库 for (int db_id 0; db_id server.dbnum; db_id) { redisDb* db server.db[db_id]; if (dictSize(db-dict) 0) continue; // 空数据库跳过 // 写入 SELECT db_id 命令 writeSelectCommand(fp, db_id); // 3. 遍历当前数据库的所有键 dictIterator* di dictGetIterator(db-dict); dictEntry* de; while ((de dictNext(di)) ! NULL) { robj* key dictGetKey(de); robj* val dictGetVal(de); // 4. 根据键的类型生成对应的重建命令 if (val-type OBJ_STRING) { // 字符串SET key value writeSetCommand(fp, key, val); } else if (val-type OBJ_LIST) { // 列表RPUSH key value1 value2 ... writeListCommand(fp, key, val); } else if (val-type OBJ_SET) { // 集合SADD key member1 member2 ... writeSetMembersCommand(fp, key, val); } else if (val-type OBJ_ZSET) { // 有序集合ZADD key score1 member1 score2 member2 ... writeZsetCommand(fp, key, val); } else if (val-type OBJ_HASH) { // 哈希HMSET key field1 value1 field2 value2 ... writeHashCommand(fp, key, val); } } dictReleaseIterator(di); } // 5. 写入 EOF 标记和校验和可选 writeEofAndChecksum(fp); // 6. 关闭临时文件 fclose(fp); }子进程遍历的是fork()那一刻的内存快照利用了写时复制技术所以数据是一致的。生成的是RESP协议格式的命令与普通 AOF 文件格式完全兼容。3.3 收尾阶段父进程处理增量数据在子进程工作期间父进程继续处理客户端请求新的写命令被同时写入两个地方旧的 AOF 缓冲区保持旧文件持续更新AOF 重写缓冲区aof_rewrite_buf子进程完成工作后通知父进程进行最后的合并// 伪代码父进程处理重写完成信号 void handleRewriteCompletion(pid_t child_pid) { // 1. 等待子进程结束获取退出状态 int status; waitpid(child_pid, status, 0); // 2. 检查子进程是否成功 if (!WIFEXITED(status) || WEXITSTATUS(status) ! 0) { // 子进程失败清理资源 clearRewriteResources(); return; } // 3. 将重写缓冲区增量数据追加到临时文件 // 这是关键步骤保证新文件包含子进程工作期间的所有新写命令 FILE* temp_fp fopen(temp-rewrite.aof, a); fwrite(server.aof_rewrite_buf, len, 1, temp_fp); fclose(temp_fp); // 4. 重命名临时文件为目标 AOF 文件原子操作 // rename 在 Unix 中是原子的 if (rename(temp-rewrite.aof, appendonly.aof) ! 0) { // 重命名失败记录错误 logError(Failed to rename AOF file); return; } // 5. 清空重写缓冲区重置状态 sdsfree(server.aof_rewrite_buf); server.aof_rewrite_buf NULL; server.aof_rewrite_child_pid -1; // 6. 如果新的 AOF 文件大于旧文件可能需要调整自动重写阈值 updateAofRewriteThreshold(); }收尾阶段的时序图时间线 ────────────────────────────────────────────────────────────── 主进程 │ fork() │ 继续处理请求写入旧AOF 重写缓冲区 │ 信号处理 │ │ │ 子进程 │ │ 遍历内存生成新AOF │ 退出 │ │ │ │───────┤ 重写缓冲区积累增量命令 │ │ │ │ │ │ │ 追加增量 │ │ │ 原子替换四、关键机制深度解析4.1 写时复制Copy-on-Writefork()创建子进程时子进程与父进程共享同一份物理内存只有当某一方修改时才会复制内存页。这使得 AOF 重写几乎不消耗额外内存是高效的异步处理基础。4.2 重写缓冲区的必要性为什么需要单独的重写缓冲区而不能用现有的 AOF 缓冲区因为子进程生成的新 AOF 文件是基于fork()时刻的数据快照。如果在子进程运行期间父进程的新命令只写入旧 AOF 缓冲区而子进程生成的临时文件没有这些命令替换后就会导致增量数据丢失。4.3 为什么重写能减少文件体积场景旧 AOF 记录重写后记录键反复修改SET k 1→SET k 2→ ... →SET k 100SET k 100只保留最终值集合频繁增删SADD s a b→SREM s a→SADD s cSADD s b c只保留最终成员列表大量增删RPUSH l a→LPUSH l b→RPOP lRPUSH l b a只保留最终元素过期键键已过期但旧文件中仍有记录直接忽略不写入新文件键被删除SET k 1→DEL k直接忽略最终状态是不存在4.4 自动触发的条件Redis 配置了两个参数控制自动重写# redis.confauto-aof-rewrite-min-size 64mb # AOF 文件至少达到 64MBauto-aof-rewrite-percentage 100 # 比上次重写后增长 100%触发逻辑的伪代码// 伪代码检查是否需要触发自动重写 void checkAndTriggerAofRewrite() { // 获取当前 AOF 文件大小 long long current_size getAofCurrentSize(); long long base_size server.aof_rewrite_base_size; // 检查条件 if (current_size server.aof_rewrite_min_size current_size base_size * (1 server.aof_rewrite_percentage / 100.0)) { // 触发重写 rewriteAppendOnlyFileBackground(); server.aof_rewrite_base_size current_size; // 更新基准大小 } }4.5 重写对性能的影响方面影响缓解措施CPU子进程遍历数据库CPU 密集后台执行不阻塞主进程内存fork()时复制页表写时复制增加内存控制重写触发频率I/O写入新 AOF 文件可配置aof-rewrite-incremental-fsync降低磁盘压力主线程仅在信号处理时短暂阻塞毫秒级几乎无感知五、总结与最佳实践5.1 AOF 重写本质AOF 重写 后台子进程 × (遍历内存 生成命令) 重写缓冲区 × (记录增量) 原子替换5.2 核心工作清单步骤执行主体关键操作1. 触发重写主进程检查条件fork()2. 生成新 AOF子进程遍历数据库为每个键生成重建命令3. 缓存增量命令主进程新写命令存入aof_rewrite_buf4. 追加增量主进程重写缓冲区数据追加到临时文件5. 原子替换主进程rename()替换旧 AOF 文件AOF重写和生成RDB快照在执行机制上确实很相似都利用了Linux的“写时复制”Copy-On-Write, COW技术来创建一个子进程处理任务避免阻塞主进程。不过它们诞生的目的和最终的结果完全不同。你可以这样理解它们的关系它们就像同一位厨师Redis主进程使用的两种不同菜谱RDB快照是每隔一段时间拍一张厨房全貌的照片存起来。这张照片RDB文件记录的是某个瞬间所有食材数据的最终样子是全量备份。AOF重写则是当记录做菜步骤的本子AOF文件变得太厚时厨师会回顾本子上的所有步骤重新梳理成一份精简版的“最终操作指南”。它只记录让食材变成当前状态所需的最小必要步骤目的是给AOF文件“减肥”。对比维度RDB 快照AOF 重写核心目的全量备份生成一个压缩的二进制文件用于快速恢复数据和灾难备份。日志瘦身压缩AOF文件体积避免其无限膨胀同时不影响持久化的实时性。产出内容数据本身某个时间点的所有键值对按Redis的二进制格式存储。写命令集合能还原当前数据集的最小、最精简的命令集合。数据完整性可能丢失数据因为是定时备份两次快照间的数据在故障时可能会丢失。保证完整性重写期间的新命令会被额外记录重写完成后会追加到新文件中确保数据不丢。恢复速度快直接加载数据文件到内存不执行任何命令。慢需要逐条重新执行文件中的所有写命令来重建数据推荐一个零声教育学习教程个人觉得老师讲得不错分享给大家[LinuxNginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDK等技术内容点击立即学习:链接

相关新闻

OpenCore Legacy Patcher完全指南:让老旧Mac焕发新生的终极免费工具

OpenCore Legacy Patcher完全指南:让老旧Mac焕发新生的终极免费工具

OpenCore Legacy Patcher完全指南:让老旧Mac焕发新生的终极免费工具 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为老旧Mac无法升级最新ma…

2026/10/20 12:56:36 阅读更多 →
山海万灵 HarmonyOS 文化知识设计续篇(23):发布候选、灰度、回滚与观察期门禁

山海万灵 HarmonyOS 文化知识设计续篇(23):发布候选、灰度、回滚与观察期门禁

一次内容更新可能同时改变图鉴正文、神兽关系、讲解提示词和客户端展示。若这些变化只靠“打一个新包、发布一批内容”分别推进,出现异常时很难判断该停掉应用版本、撤回内容,还是切回讲解策略。更稳妥的做法,是把它们收束为一份可追溯的发布…

2026/10/19 17:35:29 阅读更多 →
后端开发者必知:Airflow任务编排与MWAA实战指南

后端开发者必知:Airflow任务编排与MWAA实战指南

1. 为什么后端开发者需要掌握Airflow? 大数据时代下,后端开发者经常面临这样的困境:凌晨三点被报警短信吵醒,发现昨晚的数据批处理任务又失败了;业务部门抱怨报表数据延迟了6小时;团队里没人能说清楚各个ET…

2026/10/18 5:16:14 阅读更多 →

最新新闻

CEF 3.2357 Windows 32位编译与NPAPI插件加载实战

CEF 3.2357 Windows 32位编译与NPAPI插件加载实战

简介:这份资源是CEF(Chromium Embedded Framework)3.2357.1271.g8e0674e的Windows 32位官方二进制包备份,面向需要在C项目中嵌入浏览器内核、且依赖NPAPI插件能力的开发者。由于官方渠道已无法下载,该版本成为CEF支持N…

2026/10/20 23:49:47 阅读更多 →
Softmax输出转分类标签并绘制混淆矩阵:模型评估可视化指南

Softmax输出转分类标签并绘制混淆矩阵:模型评估可视化指南

1. 项目拆解:从概率到标签,混淆矩阵的最后一公里先把这个标题拆开来看:将softmax输出转换为分类标签进行画出混淆矩阵。这半句话里其实藏了两个动作——先把模型的输出做一次“翻译”,把向量变成标签;再把标签变成一张…

2026/10/20 23:49:47 阅读更多 →
Sci365文献管理工具解析:定位、边界与高频使用指南

Sci365文献管理工具解析:定位、边界与高频使用指南

我第一次看到“Sci365”这个名字时,说实话并没有马上明白它是干嘛的。在课题组里用了一段时间之后,我逐渐摸清了它的脾气。如果你也正在检索“Sci365是什么”“要不要把文献管理换成它”,那么这篇可以帮你省掉不少试错时间。简单说&#xff0…

2026/10/20 23:49:47 阅读更多 →
本地部署开源大模型安全加固指南:攻击面、防护与应急响应

本地部署开源大模型安全加固指南:攻击面、防护与应急响应

我接手过一台内部 GPU 服务器,用来跑某个开源 R1 系列推理项目(内部代号 FlashAI)。机器在内网,按通常说法“断网即安全”,结果我在上线前做安全审查时,发现这台机器至少有七类高优先级的风险点&#xff0c…

2026/10/20 23:49:47 阅读更多 →
SpringBoot+Vue文创推荐系统冷启动与长尾优化实战

SpringBoot+Vue文创推荐系统冷启动与长尾优化实战

简介:这是一套基于Spring Boot与Vue.js开发的热门文创内容推荐平台源码,面向Java与前端初学者、课程设计学生及毕业设计开发者,解决前后端分离项目从零搭建、内容推荐逻辑实现与工程化部署的学习痛点。资源包含可直接运行的完整源码、MySQL建…

2026/10/20 23:49:47 阅读更多 →
光热电站-ORC-P2G综合能源系统优化调度建模与Matlab实现

光热电站-ORC-P2G综合能源系统优化调度建模与Matlab实现

1. 这套系统到底在做什么:CSPORCP2G的互补逻辑 先说结论:光热电站、有机朗肯循环、P2G这三样东西放一起,核心解决的不是“多发电”,而是“让每个能量品种都在最该出现的时刻出现”。 我做过的某个模拟园区项目里,光伏…

2026/10/20 23:48:44 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →