
目录前言开局一张图1、为什么分布式事务如此之难2、2PC最朴素的契约最沉重的代价2.1 协议本身2.2 深层缺陷不是实现问题是协议本身的问题2.3 2PC适合什么场景3、3PC打补丁的艺术以及为何补丁不够3.1 三个阶段3.2 为什么3PC仍然不完美4、TCC从数据库层退回到业务层4.1 三个动作4.2 三个必须解决的工程问题5、Saga承认最终一致性拥抱补偿的哲学5.1 核心思想5.2 两种编排方式5.3 Saga的代价隔离性的缺失6、四种协议的本质对比7、真正的工程智慧不是选择而是组合8、结语与不确定性共存前言开局一张图1、为什么分布式事务如此之难在单机数据库的世界里事务是一个优雅的抽象。ACID四个字母像四堵墙一样把数据的混乱隔绝在外。你提交世界改变你回滚一切如初。但当系统跨越了进程边界、机器边界、数据中心边界这堵墙就开始漏风了。问题的根源是CAP定理和网络的不可靠性共同造成的。网络可能延迟可能丢包可能分区——而在这种不确定性下你需要让多个独立的节点就这件事是否发生了达成共识。这本质上是一个共识问题Consensus Problem。分布式事务的所有协议都是在用不同的工程哲学回答同一个哲学问题在不确定的世界里如何达成确定的承诺2、2PC最朴素的契约最沉重的代价2.1 协议本身两阶段提交的逻辑极其直觉在做决定之前先问所有人你准备好了吗。Prepare阶段协调者向所有参与者发送Prepare请求参与者执行本地事务但不提交写Undo/Redo日志回复Yes或No。Commit阶段若所有参与者都回复Yes协调者发送Commit任何一个No则发送Rollback。这个逻辑如此自然以至于很多人第一次见到2PC时会觉得这不就解决了吗。2.2 深层缺陷不是实现问题是协议本身的问题2PC的问题不是工程实现不够好而是协议在设计上存在无法绕过的理论缺陷。第一同步阻塞BlockingPrepare阶段之后参与者进入一种僵持状态——它已经锁定了资源但必须等待协调者的最终指令才能释放。此时若协调者崩溃参与者将永远持锁等待。没有任何超时机制能安全解决这个问题因为超时后自行决定提交或回滚都可能与其他节点不一致。这种阻塞不是性能问题是正确性的代价。参与者没有足够的信息独立决策。第二协调者单点Single Point of Failure协调者掌握着全局的决策权。它崩溃于Commit阶段之后、但只通知了部分参与者之前会出现最糟糕的情况部分节点已提交部分节点仍在等待。这是真正的数据不一致且难以自动恢复——需要人工介入或依赖协调者重启后从日志中恢复并重发消息。第三数据不一致的时间窗口即使一切正常Commit消息也是逐个发送的存在一个极短但真实存在的窗口使得部分提交成为可能的中间态。2.3 2PC适合什么场景尽管如此2PC并非一无是处。在网络可靠、节点稳定、事务短暂的场景下如同一数据中心内的数据库集群2PC的强一致性保证非常宝贵。MySQL的XA事务就是2PC的工业实现。理解它的边界才是使用它的前提。3、3PC打补丁的艺术以及为何补丁不够3PC的诞生是对2PC阻塞问题的正面回应。它引入了第三阶段——PreCommit——试图解决参与者在Prepare后无法独立决策的问题。3.1 三个阶段CanCommit协调者询问参与者是否有能力提交不锁资源参与者只回答能/不能。PreCommit若所有参与者都回答能则锁资源并进入预提交状态若此时超时参与者可以安全地自行回滚因为真正的锁还没加。DoCommit真正提交。关键的改进在于当参与者在DoCommit阶段超时它可以选择自行提交——因为能进入DoCommit阶段意味着所有人都已经同意了PreCommit提交是大概率正确的决定。3.2 为什么3PC仍然不完美这里有一个微妙的逻辑陷阱。3PC用超时后自行提交来打破阻塞但这个设计在网络分区场景下会翻车设想协调者在DoCommit阶段向部分参与者发送了Rollback因为某个参与者在PreCommit阶段出现了问题但网络分区导致另一部分参与者没有收到这个Rollback——它们超时后自行提交了。结果部分节点提交部分节点回滚。3PC把2PC的阻塞问题换成了不一致问题只是在不同的权衡点上做了选择。这也揭示了一个深刻的真相在异步网络模型下不存在完美的共识协议FLP不可能定理的工程体现。所有协议都是在可用性、一致性、性能之间的取舍。4、TCC从数据库层退回到业务层2PC和3PC都在试图在基础设施层解决分布式事务问题。TCC换了一个思路把事务的控制权还给业务层。4.1 三个动作Try资源预留。不直接操作最终数据而是冻结资源如不直接扣款100元而是标记冻结资金100元。Confirm确认执行。Try成功后正式提交业务操作冻结转为真实扣款。Cancel取消执行。Try失败或超时后释放预留资源解冻资金。TCC本质上是把ACID中的隔离性从数据库下沉到了业务语义层面资源预留就是隔离Confirm/Cancel就是提交/回滚。4.2 三个必须解决的工程问题TCC听起来优雅但实现它需要正视三个陷阱空回滚Empty CancelTry请求因网络超时未到达参与者协调者却先收到了超时并触发了Cancel。此时参与者收到Cancel请求但从未执行过Try——它必须能识别这种情况并成功地什么都不做否则后续延迟到达的Try会留下永久的预留资源。幂等IdempotencyConfirm和Cancel必须支持重试同样的请求执行多次结果必须相同。这要求业务层在数据库中记录操作状态每次执行前先检查。悬挂SuspensionTry请求在网络中延迟Cancel先于Try到达。如果参与者处理完Cancel后迟到的Try才来就会产生一个永远无法被Cancel的预留——资源被悬挂了。解决方案是Cancel执行时在数据库中记录此事务已取消后续到来的Try发现记录后直接拒绝。这三个问题每一个都不难理解但每一个都需要仔细的工程设计。TCC把分布式事务的复杂性从基础设施层移到了业务层代价是业务代码的侵入性极强——每个服务操作都要实现三个接口。5、Saga承认最终一致性拥抱补偿的哲学如果说2PC是强迫所有人同时到达终点那Saga就是允许大家分步走走错了就退回来。5.1 核心思想Saga将一个长事务拆分为一系列本地事务的有序序列T1 → T2 → T3 → … → Tn。每个本地事务都有对应的补偿操作C1, C2, C3…若Tk执行失败则按逆序执行补偿Ck-1 → Ck-2 → … → C1。没有全局锁没有Prepare阶段每个本地事务立即提交数据立刻可见。5.2 两种编排方式编排式Choreography各服务通过事件总线相互触发去中心化。优点是松耦合缺点是流程难以追踪像一场没有导演的话剧。协同式Orchestration中央Saga协调器负责按序调用各服务记录状态机。优点是流程清晰可追踪Seata等框架走的就是这条路。5.3 Saga的代价隔离性的缺失Saga最大的问题是缺乏隔离性。T1提交后其他事务就能读到这笔数据即使整个Saga最终因为T3失败而回滚。这会导致脏读Dirty Read at Business Level用户A的订单被创建了库存被扣了但支付失败导致整个Saga回滚——在这个时间窗口里其他事务可能已经基于这个幻象数据做出了决策。这要求业务设计上需要引入语义锁Semantic Lock、乐观锁、版本控制等手段来弥补隔离性缺失的影响。更重要的是补偿操作必须是可语义补偿的——有些操作本质上无法补偿如已发送的短信、已打印的发票Saga不适合这类场景。6、四种协议的本质对比维度2PC3PCTCCSaga一致性强一致强一致理论最终一致最终一致隔离性完整完整业务层隔离无隔离性能差持锁等待较差中需业务改造好无全局锁故障恢复需人工介入部分自动框架自动重试补偿自动恢复业务侵入低低极高高适合场景短事务、同机房理论意义大于实用金融强一致需求长事务、微服务7、真正的工程智慧不是选择而是组合在实际系统中没有人会单纯地选一个协议用。成熟的分布式系统通常是多种策略的组合对强一致要求高的核心链路如资金扣减用TCC接受业务侵入性的代价。对长流程、跨多服务的业务流程如订单履约用Saga接受最终一致性。对同数据中心的数据库集群用XA2PC用基础设施的稳定性换取简单性。对非核心、可容忍短暂不一致的场景用本地消息表 消息队列用最终一致性换取最高可用性。8、结语与不确定性共存分布式事务的演进史是工程师与不确定性博弈的历史。2PC说我要所有人承诺然后我来拍板。 3PC说我给你留条后路超时你自己决定吧。 TCC说别依赖基础设施了把控制权还给业务。 Saga说接受世界是最终一致的出了错就补偿。没有哪一个是终极答案。真正的工程素养是理解每种协议背后的假设和代价在具体的业务约束下做出最合适的权衡。正如Leslie Lamport所说分布式系统的本质是如何在你不知道另一台机器是否还活着的情况下继续工作。所有的分布式事务协议都是在用不同方式回答这个问题——而没有一个答案是完美的。承认不完美才是分布式系统设计的第一课。-------------------------------------------感悟