<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>最终一致 on 追风笔记</title><link>https://88ok.github.io/tags/%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4/</link><description>Recent content in 最终一致 on 追风笔记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Mon, 20 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://88ok.github.io/tags/%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4/index.xml" rel="self" type="application/rss+xml"/><item><title>CQRS 与 Saga：复杂业务的读写分离与最终一致</title><link>https://88ok.github.io/columns/architecture/cqrs-saga/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/columns/architecture/cqrs-saga/</guid><description>&lt;p&gt;很多团队把 CQRS 当成「加个 MQ 同步数据」就完事，结果读模型对了、写模型乱了，长事务更无从下手。CQRS 解决「读写诉求不同」，Saga 解决「业务跨多个聚合却不能开大事务」，两者常一起出现但职责不同。&lt;/p&gt;&#10;&lt;h2 id="读写为什么要分家"&gt;读写为什么要分家&#10;&lt;/h2&gt;&#10;&lt;p&gt;写模型关心一致性与不变量，是一组小而强的聚合；读模型关心展示与组装，最好能直接查出前端要的 DTO。诉求冲突时强行共用一个模型，两边都不讨好。&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-72f7da03-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="3"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-72f7da03-fence-0-viewport" data-td-code-viewport&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;money&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 写侧：只做业务&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;findByCardNo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cardNo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;读侧&lt;/span&gt;&lt;span class="err"&gt;：&lt;/span&gt;&lt;span class="n"&gt;直接投影&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;无需聚合&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;不要为了 CQRS 而 CQRS。单表就能满足读写、流量不大的模块，分家只会制造延迟和负担。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="saga-管理长事务"&gt;Saga 管理长事务&#10;&lt;/h2&gt;&#10;&lt;p&gt;转账、跨境汇款天然跨账户跨系统，不可能用本地事务锁住。Saga 把大事务拆成一串本地事务，每步都有补偿。&lt;/p&gt;&#10;&lt;div class="td-table-scroll td-table-scroll--static"&gt;&#10;&lt;table&gt;&#10; &lt;thead&gt;&#10; &lt;tr&gt;&#10; &lt;th scope="col"&gt;模式&lt;/th&gt;&#10; &lt;th scope="col"&gt;思路&lt;/th&gt;&#10; &lt;th scope="col"&gt;适用&lt;/th&gt;&#10; &lt;/tr&gt;&#10; &lt;/thead&gt;&#10; &lt;tbody&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;编排&lt;/td&gt;&#10; &lt;td&gt;中心协调器逐步调用并补偿&lt;/td&gt;&#10; &lt;td&gt;需强管控&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;tr&gt;&#10; &lt;td&gt;协同&lt;/td&gt;&#10; &lt;td&gt;各服务靠事件触发&lt;/td&gt;&#10; &lt;td&gt;去中心低耦合&lt;/td&gt;&#10; &lt;/tr&gt;&#10; &lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;h2 id="补偿与幂等"&gt;补偿与幂等&#10;&lt;/h2&gt;&#10;&lt;p&gt;Saga 最怕「补偿自己也失败」，所以每步都要幂等：用业务流水号去重，重试不重复扣款。每步记录状态机 &lt;code&gt;PENDING -&amp;gt; DONE / COMPENSATED&lt;/code&gt;，补偿顺序与正向相反，对账做兜底而非第一道防线。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;最终一致不是「不管了」，而是把一致性从「即时」换成「可验证」：允许短暂中间态，但必须能收敛到正确态。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;落地上，CQRS 让读写各取所需，Saga 让长流程可回退，再加独立对账，复杂业务才算立得住。&lt;/p&gt;</description></item></channel></rss>