<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Fork on 止语Lab</title>
        <link>https://www.wujiachen.com.cn/tags/fork/</link>
        <description>Recent content in Fork on 止语Lab</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-cn</language>
        <lastBuildDate>Sun, 28 Jun 2026 22:59:59 +0800</lastBuildDate><atom:link href="https://www.wujiachen.com.cn/tags/fork/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>Redis 的持久化机制是什么？各自的优缺点？</title>
            <link>https://www.wujiachen.com.cn/posts/redis-persistence/</link>
            <pubDate>Sun, 28 Jun 2026 22:59:57 +0800</pubDate>
            <guid>https://www.wujiachen.com.cn/posts/redis-persistence/</guid>
            <description>&lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/cover.png&#34; alt=&#34;Featured image of post Redis 的持久化机制是什么？各自的优缺点？&#34; /&gt;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/cover.png&#34; alt=&#34;Redis 持久化机制封面 — fork 瞬间分裂&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;先说结论：三种持久化机制的优缺点，主要根因都指向同一个底层机制：fork() + COW。理解它，就能解释 RDB 的数据丢失窗口、AOF 的重写阻塞、混合持久化的折中逻辑，也能解释为什么生产环境会炸（OOM），以及为什么你对&amp;quot;AOF 不丢数据&amp;quot;的理解是错的。&lt;/p&gt;&#xA;&lt;p&gt;注意我说的是&amp;quot;主要根因&amp;quot;，不是&amp;quot;唯一根因&amp;quot;。AOF 还多出一个 fsync 策略维度，这是 fork+COW 解释不了的。这个区分很重要，后面会展开。&lt;/p&gt;&#xA;&lt;p&gt;下面会用 Docker 跑一组实测，把官方文档里的文字结论变成你能复现的数据。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;三件套（RDB/AOF/混合）的优缺点主要根因都是 fork()+COW，AOF 多一个 fsync 独立维度&lt;/li&gt;&#xA;&lt;li&gt;混合持久化不是新机制，是 AOF 重写的默认行为（5.0+ 默认启用 aof-use-rdb-preamble）&lt;/li&gt;&#xA;&lt;li&gt;写入密集场景 BGSAVE 期间 RSS 可暴涨到基准 6.41 倍，主因是父进程持续写入分配新内存，不是 COW 复制&lt;/li&gt;&#xA;&lt;li&gt;&amp;ldquo;AOF 不丢数据&amp;quot;是错的，everysec 最多丢 1 秒；&amp;ldquo;混合持久化是折中&amp;quot;也是错的，它是默认行为&lt;/li&gt;&#xA;&lt;li&gt;选型三维度：写入模式 × 内存约束 × 数据安全要求&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;一三件套是什么&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e4%b8%89%e4%bb%b6%e5%a5%97%e6%98%af%e4%bb%80%e4%b9%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一、三件套是什么&#xA;&lt;/h2&gt;&lt;p&gt;Redis 持久化有三件套：RDB、AOF、混合持久化。一句话讲清每种的本质。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;RDB（Redis Database）&lt;/strong&gt;：定时对内存数据拍快照，存成紧凑的二进制文件。类比拍照，按下快门那一刻定格。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;AOF（Append Only File）&lt;/strong&gt;：把每条写命令追加到日志，重启时重放恢复。类比录像，一直录，按策略刷盘。&lt;/p&gt;&#xA;&lt;p&gt;混合持久化就是 AOF 重写时用 RDB 格式做 base file，后续追加 AOF 增量，相当于带定妆照的录像。&lt;/p&gt;&#xA;&lt;p&gt;这里有个容易忽略的细节：Redis 4.0 引入 &lt;code&gt;aof-use-rdb-preamble&lt;/code&gt; 选项，开启后 AOF 重写产出的 base file 就是 RDB 格式。Redis 5.0+ 这个选项默认启用。也就是说，&lt;strong&gt;所谓&amp;quot;混合持久化&amp;quot;不是新机制，而是 AOF 重写的默认行为&lt;/strong&gt;。Redis 7.0 在此基础上引入了 multi-part AOF 结构，把单文件拆成 base + incr + manifest，但&amp;quot;aof-use-rdb-preamble 默认 yes&amp;quot;这件事从 5.0 就成立了。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch1-three-mechanisms.png&#34; alt=&#34;三件套机制对比：RDB 拍快照 / AOF 记日志 / 混合 = base&amp;#43;增量&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;redis-70-的-multi-part-aof&#34;&gt;&lt;a href=&#34;#redis-70-%e7%9a%84-multi-part-aof&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;Redis 7.0 的 multi-part AOF&#xA;&lt;/h3&gt;&lt;p&gt;还有个变化值得提。Redis 7.0 之前，AOF 是单个 &lt;code&gt;appendonly.aof&lt;/code&gt; 文件。7.0 改成了 multi-part 结构：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;appendonlydir/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── appendonly.aof.1.base.rdb   # base file（RDB 格式，aof-use-rdb-preamble 启用时）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── appendonly.aof.1.incr.aof   # 增量日志（AOF 命令格式）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── appendonly.aof.manifest     # 清单文件（记录当前文件结构）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;base file 是重写那一刻的内存快照，incr.aof 是重写之后的新写入。manifest 文件记录当前由哪些文件组成。这个结构的好处是：重写过程中如果崩溃，旧文件还在，不会出现&amp;quot;重写到一半文件损坏&amp;quot;的问题。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch1-multipart-aof.png&#34; alt=&#34;Redis 7.0 multi-part AOF 文件结构：base.rdb &amp;#43; incr.aof &amp;#43; manifest&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;为什么要提这个？因为很多人对 AOF 的印象还停留在&amp;quot;单个大文件&amp;quot;时代。7.0+ 的 AOF 已经不是那个样子了。&lt;/p&gt;&#xA;&lt;p&gt;我跑了一组实测验证这点：100 万 key，同样的数据，RDB 文件 24,777,889 字节，AOF 重写后的 base.rdb 也是 24,777,889 字节，本实验条件下字节级一致。AOF 总大小 = base.rdb + incr.aof（增量日志）+ manifest（清单文件）。重写后立即看，incr.aof 是 0 字节，AOF 总大小只比 RDB 多 88 字节（manifest 开销）。&lt;/p&gt;&#xA;&lt;p&gt;理解了&amp;quot;混合持久化 = AOF 重写用 RDB 做 base&amp;rdquo;，后面优缺点的逻辑就顺了。&lt;/p&gt;&#xA;&lt;h2 id=&#34;二各自的优缺点标准视角&#34;&gt;&lt;a href=&#34;#%e4%ba%8c%e5%90%84%e8%87%aa%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9%e6%a0%87%e5%87%86%e8%a7%86%e8%a7%92&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;二、各自的优缺点（标准视角）&#xA;&lt;/h2&gt;&lt;p&gt;先把官方文档的优缺点表摆出来，再用实测数据对照。&lt;/p&gt;&#xA;&lt;h3 id=&#34;rdb-的优缺点&#34;&gt;&lt;a href=&#34;#rdb-%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;RDB 的优缺点&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;文件紧凑，适合备份和灾难恢复&lt;/li&gt;&#xA;&lt;li&gt;恢复速度快（二进制格式直接加载）&lt;/li&gt;&#xA;&lt;li&gt;fork 子进程后台执行，不影响主线程性能&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;两次快照之间的写入会丢失（数据丢失窗口 = save 间隔）&lt;/li&gt;&#xA;&lt;li&gt;大数据集 fork 耗时，可能阻塞主线程&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;aof-的优缺点&#34;&gt;&lt;a href=&#34;#aof-%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;AOF 的优缺点&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;数据安全性高（最多丢 1 秒，取决于 fsync 策略）&lt;/li&gt;&#xA;&lt;li&gt;日志格式可读，可修复损坏文件&lt;/li&gt;&#xA;&lt;li&gt;重写后 base file 用 RDB 格式（5.0+ 默认），恢复速度接近 RDB&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;文件大（增量日志累积后）&lt;/li&gt;&#xA;&lt;li&gt;恢复慢（增量部分需重放命令）&lt;/li&gt;&#xA;&lt;li&gt;写入有性能开销（fsync 策略影响吞吐）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;AOF 的数据安全性由 fsync 策略决定，这是 AOF 独立于 fork+COW 的维度：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;fsync 策略&lt;/th&gt;&#xA;          &lt;th&gt;含义&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;丢失窗口&lt;/th&gt;&#xA;          &lt;th&gt;性能影响&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;always&lt;/td&gt;&#xA;          &lt;td&gt;每条命令 fsync&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;理论零丢失&lt;/td&gt;&#xA;          &lt;td&gt;吞吐量大幅下降&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;everysec&lt;/td&gt;&#xA;          &lt;td&gt;每秒 fsync 一次&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;最多 1 秒&lt;/td&gt;&#xA;          &lt;td&gt;性能损失很小&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;no&lt;/td&gt;&#xA;          &lt;td&gt;由 OS 决定&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;可能数十秒&lt;/td&gt;&#xA;          &lt;td&gt;性能最好&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch2-fsync-strategies.png&#34; alt=&#34;fsync 三策略对比：always / everysec / no&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;大多数人选 everysec，是性能和安全性的平衡。但要注意：everysec 承诺的是&amp;quot;最多丢 1 秒&amp;rdquo;，不是&amp;quot;零丢失&amp;quot;。这个区别在第五节会展开。&lt;/p&gt;&#xA;&lt;h3 id=&#34;混合持久化的优缺点&#34;&gt;&lt;a href=&#34;#%e6%b7%b7%e5%90%88%e6%8c%81%e4%b9%85%e5%8c%96%e7%9a%84%e4%bc%98%e7%bc%ba%e7%82%b9&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;混合持久化的优缺点&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;兼具 RDB 的恢复速度和 AOF 的数据安全性&lt;/li&gt;&#xA;&lt;li&gt;Redis 7.0+ 默认启用，无需额外配置&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;依然要 fork（AOF 重写触发）&lt;/li&gt;&#xA;&lt;li&gt;增量部分仍是 AOF 格式，长期运行后文件会增长&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;实测与推演对照&#34;&gt;&lt;a href=&#34;#%e5%ae%9e%e6%b5%8b%e4%b8%8e%e6%8e%a8%e6%bc%94%e5%af%b9%e7%85%a7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;实测与推演对照&#xA;&lt;/h3&gt;&lt;p&gt;Docker 跑了 100 万 key 的对照实验，验证几个关键优缺点：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;指标&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;RDB&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;AOF（重写后）&lt;/th&gt;&#xA;          &lt;th&gt;说明&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;文件大小&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;24,777,889 字节&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;24,777,977 字节&lt;/td&gt;&#xA;          &lt;td&gt;AOF 多 88 字节（manifest），base.rdb 完全一致&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;恢复速度&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;快&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;推演：base 部分同 RDB，增量部分需重放&lt;/td&gt;&#xA;          &lt;td&gt;aof-use-rdb-preamble 让 base 用 RDB 格式&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;从文件大小看，aof-use-rdb-preamble 启用后，AOF 的 base file 与 RDB 文件大小完全一致。网上常见说法是&amp;quot;AOF 文件比 RDB 大很多&amp;quot;，这只在增量日志累积后才成立。刚重写完的 AOF，base 部分就是 RDB。&lt;/p&gt;&#xA;&lt;p&gt;恢复速度这部分没能实测到。我试图用 Redis 的 &lt;code&gt;loading_total_time&lt;/code&gt; 指标测恢复时间，但 Redis 7.4 加载速度极快，100 万 key 在毫秒级完成，轮询未能捕捉到加载过程。Redis 没有暴露加载过程的细粒度时间指标，&lt;code&gt;loading_total_time&lt;/code&gt; 是完成后才填入的累计值，无法在加载过程中采样。基于文件结构分析：aof-use-rdb-preamble 启用后，AOF base 部分加载速度与 RDB 相同；差异来自 incr.aof 增量命令重放。生产环境 AOF 持续运行时 incr.aof 会累积，恢复时间通常长于纯 RDB（刚重写完 incr.aof 为空时，恢复时间与纯 RDB 几乎相同）。这是推演，不是实测，但逻辑成立。&lt;/p&gt;&#xA;&lt;h2 id=&#34;三底层根因fork--cow&#34;&gt;&lt;a href=&#34;#%e4%b8%89%e5%ba%95%e5%b1%82%e6%a0%b9%e5%9b%a0fork--cow&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;三、底层根因：fork() + COW&#xA;&lt;/h2&gt;&lt;p&gt;为什么 RDB 和 AOF 的优缺点有那么多相似之处？因为它们都靠同一个机制实现后台持久化：fork() + COW。&lt;/p&gt;&#xA;&lt;p&gt;Redis 做 BGSAVE 或 AOF 重写时，主进程 fork 出一个子进程，父子进程共享内存页，子进程负责把内存数据写到磁盘文件。写入时通过 COW（Copy-on-Write，写时复制）复制被写入的页，这样子进程能安全访问 fork 那一刻的内存快照，父进程继续处理客户端请求。&lt;/p&gt;&#xA;&lt;p&gt;机制本身很优雅：持久化不阻塞主线程，子进程拿快照写文件，父进程照常工作。问题出在&amp;quot;父进程继续写入&amp;quot;这一点上。&lt;/p&gt;&#xA;&lt;h3 id=&#34;fork-的两个代价&#34;&gt;&lt;a href=&#34;#fork-%e7%9a%84%e4%b8%a4%e4%b8%aa%e4%bb%a3%e4%bb%b7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;fork() 的两个代价&#xA;&lt;/h3&gt;&lt;p&gt;fork() 不是免费的，它有两个代价：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;代价一：内存翻倍风险&lt;/strong&gt;。fork 那一刻，子进程和父进程共享所有内存页。之后父进程继续写入，写入的页通过 COW 复制。如果 fork 期间写入量大，RSS 会显著增长，具体增长多少取决于写入模式，第四节会用实测数据说明。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;代价二：主线程阻塞&lt;/strong&gt;。fork() 系统调用本身是同步的，在父进程执行期间，主线程被阻塞。Redis 单线程模型下，这意味着所有客户端请求都要等 fork 完成。fork 阻塞期间客户端请求会被排队等待，阻塞过久可能触发客户端超时重连。fork 耗时与数据集大小正相关，数据集越大，fork 越慢，阻塞越久。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch3-fork-costs.png&#34; alt=&#34;fork 的两个代价：内存翻倍风险 &amp;#43; 主线程阻塞&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;实测了不同数据集的 fork 耗时：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;数据集&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;latest_fork_usec&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;fork 耗时&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;243μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.24ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;10 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;168μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.16ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;100 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;403μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.40ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;10 万 key 比 1 万 key 还快，这有点反直觉。这是单次测量的波动，n=1 不能作为定量依据。100 万 key 时 fork 耗时 0.40ms，比 1 万 key 高一个量级，但 1 万→10 万的方向反转说明容器调度噪声不可忽略。生产环境 10GB+ 的 Redis 实例，fork 耗时可能达到百毫秒级（这是社区共识的方向性判断，本文未做大实例实测），这就是为什么大实例会出延迟尖峰。&lt;/p&gt;&#xA;&lt;h3 id=&#34;rdb-和-aof-怎么用-forkcow&#34;&gt;&lt;a href=&#34;#rdb-%e5%92%8c-aof-%e6%80%8e%e4%b9%88%e7%94%a8-forkcow&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;RDB 和 AOF 怎么用 fork+COW&#xA;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;RDB&lt;/strong&gt; 触发 BGSAVE → fork() → 子进程遍历内存写 dump.rdb。fork 频繁程度取决于 save 规则（如 &lt;code&gt;save 900 1&lt;/code&gt; = 900 秒内至少 1 个变更就触发）。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;AOF 重写&lt;/strong&gt; 触发 BGREWRITEAOF → fork() → 子进程把当前内存转成新 AOF（aof-use-rdb-preamble 启用时用 RDB 格式做 base）→ 重写完成后替换旧 AOF。AOF fork 较少，只在重写时触发。&lt;/p&gt;&#xA;&lt;p&gt;混合持久化就是 AOF 重写用 RDB 做 base，机制上和 AOF 重写是同一回事。&lt;/p&gt;&#xA;&lt;p&gt;三者都绕不开 fork+COW。这就是为什么它们的优缺点有对称性：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;RDB fork 频繁（按 save 规则）/ AOF fork 较少（仅重写时），但都要 fork&lt;/li&gt;&#xA;&lt;li&gt;RDB 数据丢失窗口 = save 间隔 / AOF 数据丢失窗口 = fsync 间隔，不同的丢失机制&lt;/li&gt;&#xA;&lt;li&gt;RDB 文件小 = 二进制紧凑 / AOF 文件大 = 增量日志冗余，但 base 部分都是 RDB&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;一个重要的修正fsync-是-aof-独立的维度&#34;&gt;&lt;a href=&#34;#%e4%b8%80%e4%b8%aa%e9%87%8d%e8%a6%81%e7%9a%84%e4%bf%ae%e6%ad%a3fsync-%e6%98%af-aof-%e7%8b%ac%e7%ab%8b%e7%9a%84%e7%bb%b4%e5%ba%a6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;一个重要的修正：fsync 是 AOF 独立的维度&#xA;&lt;/h3&gt;&lt;p&gt;三件套优缺点不能完全归因到 fork+COW。AOF 的&amp;quot;慢&amp;quot;来自两个独立机制：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;fsync 策略&lt;/strong&gt;（always / everysec / no），磁盘 I/O 操作，与 fork+COW 无关（策略详情见第二节 fsync 表）&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;fork + COW&lt;/strong&gt;（AOF 重写时），内存操作&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;RDB 只有 fork+COW 一个维度。AOF 是双根因：fsync 策略（日常写入）+ fork+COW（重写时）。混合持久化的增量部分仍是 AOF，所以也是双根因。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;fsync 策略是 AOF 独有的维度，不能用 fork+COW 解释。&lt;/strong&gt; 这个区分让论点更精确，避免把所有问题都归因到 fork+COW 上。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch3-fork-cow.png&#34; alt=&#34;fork()&amp;#43;COW 机制时序：fork 触发 → 共享内存页 → 父进程写入 → COW 复制完成&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;四为什么会炸forkcow-触发-oom-的真实事故链&#34;&gt;&lt;a href=&#34;#%e5%9b%9b%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bc%9a%e7%82%b8forkcow-%e8%a7%a6%e5%8f%91-oom-%e7%9a%84%e7%9c%9f%e5%ae%9e%e4%ba%8b%e6%95%85%e9%93%be&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;四、为什么会炸：fork/COW 触发 OOM 的真实事故链&#xA;&lt;/h2&gt;&lt;p&gt;机制本身没问题，问题出在写入密集场景。数据比我预期的更激烈。&lt;/p&gt;&#xA;&lt;h3 id=&#34;实测写入密集--fork-期间-rss-暴涨-641-倍&#34;&gt;&lt;a href=&#34;#%e5%ae%9e%e6%b5%8b%e5%86%99%e5%85%a5%e5%af%86%e9%9b%86--fork-%e6%9c%9f%e9%97%b4-rss-%e6%9a%b4%e6%b6%a8-641-%e5%80%8d&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;实测：写入密集 + fork 期间 RSS 暴涨 6.41 倍&#xA;&lt;/h3&gt;&lt;p&gt;实验设计：Docker 起 Redis 7.4，先灌入 50 万 key（基础内存约 39.4MB），然后启动 200 万 key 的写入负载，同时触发 BGSAVE，每秒采样 &lt;code&gt;INFO memory&lt;/code&gt; 的 &lt;code&gt;used_memory_rss&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;结果：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;采样点&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;used_memory_rss&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;相对基准 used_memory&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;BGSAVE 前&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;49MB&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;100%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;+1s&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;120MB&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;292%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;+2s&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;230MB&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;557%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;+3s&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;252MB（峰值）&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;641%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;+4s~+10s&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;252MB&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;641%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;RSS 从 49MB 涨到 252MB，是基准 used_memory 的 6.41 倍。如果这个 Redis 跑在内存上限 240MB 的容器里，BGSAVE 期间直接 OOM。&lt;/p&gt;&#xA;&lt;p&gt;实验设计说明：这组实验同时启动了写入负载和 BGSAVE，没有单独跑&amp;quot;仅写入不 BGSAVE&amp;quot;的对照组，因此 6.41 倍中 fork COW 与写入负载各自的占比无法精确拆分。F3 空闲场景对照（无写入时 BGSAVE，RSS 仅增长 0.28%）只能证明&amp;quot;无写入时 RSS 几乎不涨&amp;quot;，不能反向推出&amp;quot;写入负载是主因&amp;quot;。下一节的&amp;quot;诚实修正&amp;quot;基于 COW 复制量数据（1.1%）做的归因，是一个方向性判断，不是精确的定量拆分。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch4-rss-curve.png&#34; alt=&#34;fork 期间 RSS 变化曲线：写入密集场景 vs 空闲场景&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;诚实修正rss-暴涨的主因不是-cow-复制&#34;&gt;&lt;a href=&#34;#%e8%af%9a%e5%ae%9e%e4%bf%ae%e6%ad%a3rss-%e6%9a%b4%e6%b6%a8%e7%9a%84%e4%b8%bb%e5%9b%a0%e4%b8%8d%e6%98%af-cow-%e5%a4%8d%e5%88%b6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;诚实修正：RSS 暴涨的主因不是 COW 复制&#xA;&lt;/h3&gt;&lt;p&gt;这里要做一个重要的诚实修正。&lt;/p&gt;&#xA;&lt;p&gt;我最初以为 RSS 暴涨是 COW 复制导致的——fork 后父进程写入，每���被写入的页都复制一份，RSS 翻倍。但实测数据不支持这个判断：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;rdb_last_cow_size&lt;/code&gt;（COW 复制量）：462,848 字节（约 0.44MB）&lt;/li&gt;&#xA;&lt;li&gt;占 used_memory 比例：1.1%&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;COW 只复制了 0.44MB，但 RSS 涨了 200MB+。这说明 &lt;strong&gt;RSS 暴涨的主因不是 COW 复制&lt;/strong&gt;，而是写入密集负载本身在持续分配新内存（200 万新 key 写入，每个 key-value 占用内存，父进程 RSS 持续增长）。fork 期间父进程继续接收写入，子进程持有旧页快照，父进程新写入分配新页，两部分都计入 RSS。而 COW 只在写入已存在页时触发，新 key 是分配新页不触发 COW，只有修改已有 key 才触发 COW。&lt;/p&gt;&#xA;&lt;p&gt;这里要做一个场景限定声明：本实验用 redis-benchmark 写新 key，COW 占比低是预期行为（新 key 分配新页，不触发 COW）。若实验改为修改已有 key，COW 占比会上升。本实验的&amp;quot;修正&amp;quot;结论限定于&amp;quot;写入密集负载以新 key 为主&amp;quot;的场景，不适用于&amp;quot;频繁修改已有 key&amp;quot;的场景。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch4-rss-breakdown.png&#34; alt=&#34;RSS 暴涨主因拆解：父进程持续写入 98.9% vs COW 复制 1.1%&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;对照实验（F3 证伪）：空闲无写入场景下 BGSAVE，RSS 仅增长 0.28%，COW 复制量也是 1.1%。这验证了&amp;quot;翻倍是写入密集场景特定现象&amp;quot;。&lt;/p&gt;&#xA;&lt;h3 id=&#34;生产环境-oom-的真正机制&#34;&gt;&lt;a href=&#34;#%e7%94%9f%e4%ba%a7%e7%8e%af%e5%a2%83-oom-%e7%9a%84%e7%9c%9f%e6%ad%a3%e6%9c%ba%e5%88%b6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;生产环境 OOM 的真正机制&#xA;&lt;/h3&gt;&lt;p&gt;基于实测修正后的理解，fork 期间 OOM 的真正机制是：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;fork() 触发，子进程持有当前内存快照&lt;/li&gt;&#xA;&lt;li&gt;父进程继续接收写入，&lt;strong&gt;新写入分配新内存页&lt;/strong&gt;（这是 RSS 增长主因）&lt;/li&gt;&#xA;&lt;li&gt;父进程修改已有 key，触发 COW 复制（次因，实测仅 1.1%）&lt;/li&gt;&#xA;&lt;li&gt;RSS = 子进程快照 + 父进程新写入 + COW 复制 &amp;gt; 物理内存上限&lt;/li&gt;&#xA;&lt;li&gt;cgroup OOM killer 触发，Redis 进程被杀&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;关键监控指标有三个：&lt;code&gt;rdb_last_cow_size&lt;/code&gt; / &lt;code&gt;aof_last_cow_size&lt;/code&gt;（COW 复制量，次因）、&lt;code&gt;used_memory_rss&lt;/code&gt; 在 BGSAVE/AOF_REWRITE 期间的增长（主因）、&lt;code&gt;used_memory&lt;/code&gt; 与 &lt;code&gt;maxmemory&lt;/code&gt; 的距离（缓冲空间）。&lt;/p&gt;&#xA;&lt;h3 id=&#34;另一个独立的内存风险aof-重写缓冲区&#34;&gt;&lt;a href=&#34;#%e5%8f%a6%e4%b8%80%e4%b8%aa%e7%8b%ac%e7%ab%8b%e7%9a%84%e5%86%85%e5%ad%98%e9%a3%8e%e9%99%a9aof-%e9%87%8d%e5%86%99%e7%bc%93%e5%86%b2%e5%8c%ba&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;另一个独立的内存风险：AOF 重写缓冲区&#xA;&lt;/h3&gt;&lt;p&gt;除了 fork+COW，AOF 重写期间还有一个独立的内存风险源。重写期间父进程新写入的命令会同时写到旧 AOF 和 aof_rewrite_buffer 中（用于重写完成后追加到新 AOF 末尾）。如果重写耗时长且写入量大，这个 buffer 会持续增长。这是 fork+COW 之外的正交维度——即使 COW 复制量很小，重写缓冲区仍可能占用大量内存。监控时除了 &lt;code&gt;aof_last_cow_size&lt;/code&gt;，也要关注重写期间的 &lt;code&gt;used_memory&lt;/code&gt; 增长趋势。&lt;/p&gt;&#xA;&lt;h3 id=&#34;thp-放大效应&#34;&gt;&lt;a href=&#34;#thp-%e6%94%be%e5%a4%a7%e6%95%88%e5%ba%94&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;THP 放大效应&#xA;&lt;/h3&gt;&lt;p&gt;还有个放大器叫 THP（Transparent Huge Pages）。Linux 默认内存页 4KB，THP 把 4KB 小页合并成 2MB 大页。COW 时，即使只写一个字节，也要复制整个 2MB 页——单次 COW 复制的页大小放大 512 倍（2MB/4KB），实际 RSS 放大取决于被 COW 的大页数量。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch4-thp-amplify.png&#34; alt=&#34;THP 放大效应：4KB 小页 vs 2MB 大页，COW 复制量放大 512 倍&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;我试图在 Docker 里实测 THP 开/关的 COW 差异，但结果与理论预期相反（可能是 macOS Docker Desktop 的 LinuxKit 内核行为与原生 Linux 不一致）。&lt;strong&gt;以下 THP 部分的实测环境为 macOS Docker Desktop，与生产 Linux 环境可能有差异，实测数据未采用，仅作理论推演 + 官方说明佐证。&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;逻辑上的放大机制是成立的：2MB 大页的 COW 复制量远大于 4KB 小页。Redis 官方在 redis.conf 中明确说明了这个风险：&lt;/p&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;&amp;ldquo;On systems in which it (THP) is set to &amp;lsquo;always&amp;rsquo;, redis will attempt to disable it specifically for the redis process in order to avoid latency problems specifically with fork(2) and CoW.&amp;rdquo;&#xA;—— Redis 7.4 redis.conf，&lt;code&gt;disable-thp&lt;/code&gt; 配置项说明&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;&lt;p&gt;Redis 7.0+ 甚至新增了 &lt;code&gt;disable-thp yes&lt;/code&gt; 配置项，让 Redis 进程启动时主动关闭自己的 THP，规避 fork+COW 的延迟问题。这是官方对&amp;quot;THP + fork + COW 有问题&amp;quot;的明确承认。&lt;/p&gt;&#xA;&lt;p&gt;生产建议：Redis 机器关掉 THP（参考 Redis 官方 redis.conf 说明与 &lt;code&gt;disable-thp&lt;/code&gt; 配置，本实验未能直接验证）。&lt;code&gt;echo never &amp;gt; /sys/kernel/mm/transparent_hugepage/enabled&lt;/code&gt;，或在 Redis 7.0+ 配置 &lt;code&gt;disable-thp yes&lt;/code&gt; 让 Redis 自己处理。&lt;/p&gt;&#xA;&lt;h2 id=&#34;五4-个认知偏差&#34;&gt;&lt;a href=&#34;#%e4%ba%944-%e4%b8%aa%e8%ae%a4%e7%9f%a5%e5%81%8f%e5%b7%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;五、4 个认知偏差&#xA;&lt;/h2&gt;&lt;p&gt;讲完机制，纠正几个常见的认知偏差，按纠正价值从高到低排列。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;偏差&lt;/th&gt;&#xA;          &lt;th&gt;常见误解&lt;/th&gt;&#xA;          &lt;th&gt;准确说法&lt;/th&gt;&#xA;          &lt;th&gt;实测证据&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;一&lt;/td&gt;&#xA;          &lt;td&gt;AOF 不丢数据&lt;/td&gt;&#xA;          &lt;td&gt;everysec 最多丢 1 秒&lt;/td&gt;&#xA;          &lt;td&gt;kill -9 实验：5 万写入丢失 1,230（2.46%）&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;二&lt;/td&gt;&#xA;          &lt;td&gt;RDB 备份够用&lt;/td&gt;&#xA;          &lt;td&gt;save 间隔内数据会丢&lt;/td&gt;&#xA;          &lt;td&gt;save 900 1 最坏丢 15 分钟&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;三&lt;/td&gt;&#xA;          &lt;td&gt;混合是折中&lt;/td&gt;&#xA;          &lt;td&gt;5.0+ 默认行为&lt;/td&gt;&#xA;          &lt;td&gt;aof-use-rdb-preamble 5.0 默认 yes&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;四&lt;/td&gt;&#xA;          &lt;td&gt;持久化无代价&lt;/td&gt;&#xA;          &lt;td&gt;fork+COW 有 OOM 风险&lt;/td&gt;&#xA;          &lt;td&gt;BGSAVE 期间 RSS 6.41 倍&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch5-myths.png&#34; alt=&#34;4 个认知偏差对照：常见误解 vs 准确说法&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;偏差一aof-不丢数据&#34;&gt;&lt;a href=&#34;#%e5%81%8f%e5%b7%ae%e4%b8%80aof-%e4%b8%8d%e4%b8%a2%e6%95%b0%e6%8d%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;偏差一：AOF 不丢数据&#xA;&lt;/h3&gt;&lt;p&gt;&amp;ldquo;AOF 不丢数据&amp;quot;是错的。这个误解在官方文档层面其实已有说明（everysec 的措辞是 &amp;ldquo;very safe&amp;rdquo; 不是 &amp;ldquo;zero loss&amp;rdquo;），但工程实践中仍有团队误以为 everysec = 不丢数据。准确说法取决于 fsync 策略：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;AOF + always：每条命令 fsync，理论零丢失，但性能大幅下降&lt;/li&gt;&#xA;&lt;li&gt;AOF + everysec：每秒 fsync 一次，&lt;strong&gt;最多丢 1 秒&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;AOF + no：由 OS 决定 fsync，可能丢数十秒&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Redis 官方文档对 everysec 的措辞是 &amp;ldquo;very safe&amp;rdquo;（非常安全），不是 &amp;ldquo;zero loss&amp;rdquo;（零丢失）。&lt;/p&gt;&#xA;&lt;p&gt;这个误解的来源：很多人把&amp;quot;AOF 比较安全&amp;quot;等同于&amp;quot;AOF 不丢数据&amp;rdquo;。官方文档说 everysec 是 &amp;ldquo;very safe&amp;rdquo;，但 &amp;ldquo;very safe&amp;rdquo; 和 &amp;ldquo;zero loss&amp;rdquo; 是两回事。销售文案和工程承诺之间有差距——工程承诺要精确到丢失窗口。&lt;/p&gt;&#xA;&lt;p&gt;我跑了一个断电模拟实验：AOF everysec 策略，灌入 10 万基础 key，启动 redis-benchmark 持续写入 5 万 key，0.5 秒后 kill -9 模拟断电。重启后检查：实际写入 48,770 key，丢失 1,230 key（2.46%）。&lt;/p&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch5-power-loss.png&#34; alt=&#34;AOF everysec 断电实验：写入中 → kill -9 模拟断电 → 重启检查丢失 2.46%&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;需要说明的是，kill -9 模拟的是进程被杀，不是真实断电。真实断电时 OS page cache 全部丢失，AOF 缓冲区未刷盘部分必然丢失，实际丢失可能更多。另外，kill -9 后 AOF 文件可能不完整（半条命令），Redis 重启时会 truncate 到最后一条完整命令，这个修复过程也可能额外丢失数据。所以 kill -9 与真实断电的丢失量方向不确定，但这次实验已经足以证明：AOF everysec 存在丢失窗口，&amp;ldquo;不丢数据&amp;quot;是错的认知。&lt;/p&gt;&#xA;&lt;p&gt;所以 AOF everysec 承诺&amp;quot;最多丢 1 秒&amp;rdquo;，这是明确的上界。如果你的业务不能容忍任何丢失，要么用 always（性能代价），要么别用 Redis 做主存储。&lt;/p&gt;&#xA;&lt;h3 id=&#34;偏差二rdb-备份够用&#34;&gt;&lt;a href=&#34;#%e5%81%8f%e5%b7%ae%e4%ba%8crdb-%e5%a4%87%e4%bb%bd%e5%a4%9f%e7%94%a8&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;偏差二：RDB 备份够用&#xA;&lt;/h3&gt;&lt;p&gt;&amp;ldquo;用 RDB 做定时备份就够了&amp;rdquo;——取决于你的业务能否容忍 save 间隔内的数据丢失。&lt;/p&gt;&#xA;&lt;p&gt;RDB 的数据丢失窗口 = save 规则间隔。save 规则是&amp;quot;变更次数 + 时间窗口&amp;quot;复合触发，如 &lt;code&gt;save 900 1&lt;/code&gt; 表示 900 秒内至少 1 个变更才触发，最坏情况是刚 save 完后 900 秒内的变更全部丢失。如果配置 &lt;code&gt;save 900 1&lt;/code&gt;（900 秒内至少 1 个变更就触发），最坏情况丢失 15 分钟数据。对于缓存场景可能没问题，对于订单、支付这类业务就是事故。&lt;/p&gt;&#xA;&lt;p&gt;这个误解的来源：很多人把&amp;quot;RDB 是快照备份&amp;quot;等同于&amp;quot;RDB 是完整备份&amp;quot;。快照是某个时间点的状态，不是连续记录。两次快照之间的变更，快照里没有。&lt;/p&gt;&#xA;&lt;p&gt;判断标准：业务能容忍&amp;quot;回到 15 分钟前&amp;quot;就用 RDB，不能就上 AOF。这是业务判断，不是技术判断——技术方案的选择应该由业务对数据丢失的容忍度驱动，而不是由&amp;quot;哪个更简单&amp;quot;驱动。&lt;/p&gt;&#xA;&lt;h3 id=&#34;偏差三混合持久化是折中&#34;&gt;&lt;a href=&#34;#%e5%81%8f%e5%b7%ae%e4%b8%89%e6%b7%b7%e5%90%88%e6%8c%81%e4%b9%85%e5%8c%96%e6%98%af%e6%8a%98%e4%b8%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;偏差三：混合持久化是折中&#xA;&lt;/h3&gt;&lt;p&gt;&amp;ldquo;混合持久化是 RDB 和 AOF 的折中&amp;rdquo;——这个说法暗示它是第三种独立机制。其实不是。&lt;/p&gt;&#xA;&lt;p&gt;如第一节所述，aof-use-rdb-preamble 5.0+ 默认启用，7.0 又引入 multi-part AOF 结构，所谓&amp;quot;混合持久化&amp;quot;就是 AOF 重写默认行为。它不是&amp;quot;折中&amp;quot;，而是&amp;quot;默认&amp;quot;。&amp;ldquo;要不要开启混合持久化&amp;quot;在 5.0+ 就是个伪问题。&lt;/p&gt;&#xA;&lt;p&gt;这个误解的来源：Redis 4.0 刚引入 aof-use-rdb-preamble 时默认是 no，那时确实需要手动开启。5.0 改为默认 yes 后成了标准行为。很多教程还停留在 4.0 时代的表述。&lt;/p&gt;&#xA;&lt;h3 id=&#34;偏差四持久化无代价&#34;&gt;&lt;a href=&#34;#%e5%81%8f%e5%b7%ae%e5%9b%9b%e6%8c%81%e4%b9%85%e5%8c%96%e6%97%a0%e4%bb%a3%e4%bb%b7&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;偏差四：持久化无代价&#xA;&lt;/h3&gt;&lt;p&gt;&amp;ldquo;开持久化总是好的&amp;rdquo;——持久化有代价。&lt;/p&gt;&#xA;&lt;p&gt;代价就是 fork+COW。第四节的实测数据：写入密集场景下 BGSAVE 期间 RSS 暴涨到基准的 6.41 倍。如果你的 Redis 跑在内存吃紧的环境，开持久化 = 增加 OOM 风险。&lt;/p&gt;&#xA;&lt;p&gt;另一个代价是 fork 阻塞。实测 fork 耗时随数据集增长：&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;数据集&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;latest_fork_usec&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: center&#34;&gt;fork 耗时&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;243μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.24ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;10 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;168μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.16ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;100 万 key&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;403μs&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: center&#34;&gt;0.40ms&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Redis 单线程模型下，fork 期间主线程阻塞。大数据集 fork 耗时可能进入毫秒甚至百毫秒级（方向性判断，本文 100 万 key 实测仅 0.40ms，更大实例未做实测），会导致延迟尖峰。&lt;/p&gt;&#xA;&lt;h2 id=&#34;六怎么选三维决策框架&#34;&gt;&lt;a href=&#34;#%e5%85%ad%e6%80%8e%e4%b9%88%e9%80%89%e4%b8%89%e7%bb%b4%e5%86%b3%e7%ad%96%e6%a1%86%e6%9e%b6&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;六、怎么选：三维决策框架&#xA;&lt;/h2&gt;&lt;p&gt;讲了这么多机制，落到&amp;quot;怎么选&amp;rdquo;。用三个维度判断（本框架是通用工程经验整理，非从本文实测数据提炼）：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;维度一：写入模式&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;写入密集（高频写、大 key）→ fork 风险高，监控 &lt;code&gt;rdb_last_cow_size&lt;/code&gt; 和 RSS 增长&lt;/li&gt;&#xA;&lt;li&gt;读多写少 → fork 风险低，RDB 足够&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;维度二：内存约束&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;内存充裕（used_memory &amp;lt; 物理内存的 50%）→ fork 期间 RSS 翻倍也有缓冲（读多写少场景适用）&lt;/li&gt;&#xA;&lt;li&gt;内存吃紧（used_memory &amp;gt; 物理内存的 70%）→ fork 期间 OOM 风险高，考虑关闭持久化或扩容&lt;/li&gt;&#xA;&lt;li&gt;写入密集场景需更保守，建议 used_memory &amp;lt; 物理内存 15-20%，或根据实测 RSS 峰值倒推（第四节实测 RSS 可达基准 6.41 倍）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;维度三：数据安全要求&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;能容忍分钟级丢失 → RDB&lt;/li&gt;&#xA;&lt;li&gt;只能容忍秒级丢失 → AOF everysec&lt;/li&gt;&#xA;&lt;li&gt;不能丢失任何数据 → 别用 Redis 做主存储，用真正的数据库&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;配置建议：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;大多数场景：AOF everysec + aof-use-rdb-preamble（5.0+ 默认）+ 关闭 THP&lt;/li&gt;&#xA;&lt;li&gt;纯缓存场景：RDB only 或关闭持久化&lt;/li&gt;&#xA;&lt;li&gt;内存敏感场景：监控 &lt;code&gt;rdb_last_cow_size&lt;/code&gt; / &lt;code&gt;aof_last_cow_size&lt;/code&gt; / &lt;code&gt;latest_fork_usec&lt;/code&gt;，设告警阈值&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&#xA;    &lt;img src=&#34;https://img.wujiachen.com.cn/redis-persistence/ch6-decision.png&#34; alt=&#34;三维决策框架：写入模式 × 内存约束 × 数据安全要求&#34; loading=&#34;lazy&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;七结语&#34;&gt;&lt;a href=&#34;#%e4%b8%83%e7%bb%93%e8%af%ad&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;七、结语&#xA;&lt;/h2&gt;&lt;p&gt;回到开头那个结论：三种持久化机制的优缺点，主要根因都指向 fork() + COW。RDB 完全靠它，AOF 重写靠它，混合持久化的 base file 生成也靠它。AOF 多了一个 fsync 策略维度，这是 fork+COW 解释不了的。&lt;/p&gt;&#xA;&lt;p&gt;如果你只能记住一件事，记住这个：fork() 不是免费的，它会让你的 RSS 在写入密集场景暴涨——但主因是父进程持续写入分配新内存，不是 COW 复制本身。下次你看 Redis INFO 输出，先看 &lt;code&gt;rdb_last_cow_size&lt;/code&gt; 和 &lt;code&gt;used_memory_rss&lt;/code&gt; 在 BGSAVE 期间的变化，再看 &lt;code&gt;latest_fork_usec&lt;/code&gt;。这些数字告诉你持久化的真实代价。&lt;/p&gt;&#xA;&lt;p&gt;以上基于 Docker 实测和官方文档梳理，生产环境的具体阈值还需按业务场景验证，欢迎讨论。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;附录实验代码和原始数据&#34;&gt;&lt;a href=&#34;#%e9%99%84%e5%bd%95%e5%ae%9e%e9%aa%8c%e4%bb%a3%e7%a0%81%e5%92%8c%e5%8e%9f%e5%a7%8b%e6%95%b0%e6%8d%ae&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;附录：实验代码和原始数据&#xA;&lt;/h2&gt;&lt;p&gt;本文 6 组实验 + 4 组证伪实验的代码和原始输出已开源：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;GitHub：&lt;a class=&#34;link&#34; href=&#34;https://github.com/wujiachen0727/zhiyulab-evidence/tree/main/redis-persistence&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;zhiyulab-evidence/redis-persistence&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;子目录说明：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e1-file-size/&lt;/code&gt; — 100 万 key RDB vs AOF 文件大小对比&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e2-recovery-time/&lt;/code&gt; — 恢复时间实验（Redis 7.4 加载太快采集失败，正文用推演）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e3-rss-spike/&lt;/code&gt; — 写入密集场景 fork 期间 RSS 暴涨实测&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e4-thp/&lt;/code&gt; — THP 放大效应（macOS Docker Desktop 环境结果与理论相反，正文降级为推演 + 官方说明）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e5-power-loss/&lt;/code&gt; — AOF everysec 断电丢失窗口模拟&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/e6-fork-duration/&lt;/code&gt; — fork 耗时随数据集变化&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;code/falsification/&lt;/code&gt; — 4 组证伪实验（F1 文件大小 / F2 恢复时间 / F3 RSS 翻倍 / F4 单一根因）&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;output/&lt;/code&gt; — 各实验的原始输出（INFO memory 采样、redis-cli 输出、文件大小记录）&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;每个子目录都有独立 README 说明如何复现。Docker 环境 macOS Docker Desktop 与生产 Linux 可能有差异，已在正文相应段落标注。&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&#xA;    &lt;blockquote&gt;&#xA;        &lt;p&gt;原文发布于 &lt;a class=&#34;link&#34; href=&#34;https://www.wujiachen.com.cn/posts/redis-persistence&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;止语Lab&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;    &lt;/blockquote&gt;&#xA;</description>
        </item></channel>
</rss>
