<?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/%E4%B8%80%E8%87%B4%E6%80%A7/</link><description>Recent content in 一致性 on 追风笔记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Mon, 23 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://88ok.github.io/tags/%E4%B8%80%E8%87%B4%E6%80%A7/index.xml" rel="self" type="application/rss+xml"/><item><title>分布式事务在金融场景的取舍</title><link>https://88ok.github.io/blog/distributed-transaction/</link><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><guid>https://88ok.github.io/blog/distributed-transaction/</guid><description>&lt;p&gt;金融系统聊分布式事务，很容易陷入&amp;quot;选哪个框架&amp;quot;的讨论。但真实的约束是：钱不能错，且必须能查清为什么错。这两条决定了方案选择的顺序和别的行业不太一样。&lt;/p&gt;&#10;&lt;h2 id="金融场景的真实约束"&gt;金融场景的真实约束&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;不允许静默不一致&lt;/strong&gt;。可以暂时不一致，但必须有机制发现并修复，不能出现谁也不知道差在哪的状态。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;必须能人工干预&lt;/strong&gt;。任何自动补偿机制都有失效的可能，最终要留一条人工处理通道，且要有审计留痕。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;联机响应时间有硬指标&lt;/strong&gt;。核心交易通常要求 100ms 内，多阶段协调的额外网络往返基本吃不下。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;补偿不总是可行&lt;/strong&gt;。已经出账到人民银行的报文没法回滚，只能发反向报文，业务语义完全不同。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;最后一条最容易被忽略：很多补偿型方案假设操作可逆，但金融业务里大量操作在真实世界里是不可逆的。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="几种方案的适用边界"&gt;几种方案的适用边界&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;2PC / XA&lt;/strong&gt;：强一致，但同步阻塞、协调者单点、锁持有时间长。只在同库多表或同一数据库集群内使用。跨服务用它，第一次网络抖动就会看到大批悬挂事务。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;TCC&lt;/strong&gt;：一致性和性能都不错，代价是每个参与方要写三份逻辑（try / confirm / cancel），且必须自己处理空回滚、悬挂、幂等。开发量大约是普通接口的三倍，适合参与方少且业务确定的核心链路。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Saga&lt;/strong&gt;：适合长流程、多参与方，比如开户流程里的证件核验、账户开立、卡片制发。要求每步都有语义补偿，且能接受中间态对外可见。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;本地消息表 + 可靠投递&lt;/strong&gt;：把消息写入和业务操作放在同一个本地事务，再由后台任务投递。实现简单、无外部依赖、可靠性靠数据库保证。缺点是只能做最终一致。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="我们的默认选择本地消息表--对账"&gt;我们的默认选择：本地消息表 + 对账&#10;&lt;/h2&gt;&#10;&lt;p&gt;绝大多数场景我们用本地消息表。理由很朴素：它没有引入新的中间件，所有状态都在业务库里，出问题时排查路径最短。&lt;/p&gt;&#10;&lt;div class="td-code td-code--untitled" id="td-code-310a34a3-fence-0" data-td-code data-td-code-auto-id&#10; data-td-language="java" data-td-line-count="15"&gt;&#10; &lt;div class="td-code__viewport" id="td-code-310a34a3-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="nd"&gt;@Transactional&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="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TransferCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 1. 本地账务操作&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Voucher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;voucher&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;accountingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toVoucher&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="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="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 2. 同一事务内写待发消息，保证原子性&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OutboxMessage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;msg&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;OutboxMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&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="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;transfer.posted&amp;#34;&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bizNo&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JsonUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;voucher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toEvent&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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outboxRepository&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;msg&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="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="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="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="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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;配套三件事一个都不能省：&lt;/p&gt;</description></item></channel></rss>