)
分布式事务完全指南XA、TCC、Saga、本地消息表与可靠消息最终一致性方案对比doocs/advanced-java【免费下载链接】advanced-java Core Interview Questions Answers For Experienced Java(Backend) Developers | 互联网 Java 工程师进阶知识完全扫盲涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识项目地址: https://gitcode.com/doocs/advanced-java分布式事务是微服务与分布式系统面试中的必考话题。本文基于 doocs/advanced-java 仓库的分布式事务原理一文系统梳理业界主流的 6 种分布式事务方案——XA 两阶段提交、TCC、Saga、本地消息表、可靠消息最终一致性、最大努力通知——逐一剖析其核心原理、执行流程、适用场景与典型坑点并结合仓库中消息可靠性、消息幂等性、接口幂等性、CAP 定理等关联文档讲清强一致性方案与最终一致性方案的选型逻辑让你在面试与真实架构设计中都能给出有据可依的答案。为什么面试必问分布式事务只要聊到分布式系统分布式事务就是绕不开的标配问题。你在单机单体架构下使用数据库本地事务即可保证一致性但一旦系统拆分成几十甚至上百个微服务、数据分散到多个数据库一次业务操作往往会跨多个服务、跨多个库执行要么全部成功、要么全部回滚的本地事务语义就失效了这就必须引入分布式事务。面试官考察这一点的核心意图是你起码得知道有哪些方案一般怎么来做每个方案的优缺点是什么。你不一定真正在生产中落地过但必须能清晰地说出备选方案的种类、各自的坑点——比如 TCC 方案的网络问题、XA 方案的一致性问题——这正是本文要解决的核心问题。从仓库的分布式系统章节索引可以看到分布式事务与分布式锁、分布式会话、幂等性、请求顺序性共同构成了分布式系统的核心难点而事务又和MQ 面试连环炮里如何保证消息不丢、不重复、有序等问题深度耦合。分布式事务实现的 6 种主流方案目前业界实现分布式事务主要有以下 6 种方案XA 方案两阶段提交TCC 方案Try / Confirm / CancelSAGA 方案长事务补偿本地消息表可靠消息最终一致性方案最大努力通知方案前三种偏强一致性 / 业务补偿路线后三种偏最终一致性 / 消息驱动路线。下面逐一展开。XA 方案两阶段提交核心原理所谓的 XA 方案即两阶段提交2PC核心是引入一个事务管理器Transaction Manager负责协调多个数据库资源管理器Resource Manager的事务第一阶段准备 / Prepare事务管理器先询问各个数据库你准备好了吗第二阶段提交 / Commit如果每个数据库都回复 ok就正式提交事务在各个数据库上执行操作如果任何一个数据库回答不 ok那么就回滚事务。从图中可以看到一个系统内部持有事务管理器事务管理器分别连接数据库 1、2、3第一阶段询问、第二阶段执行正是典型的集中式两阶段提交协调模型。适用场景与局限这种方案比较适合单块应用里跨多个库的分布式事务可以基于Spring JTA实现自行搜索 demo 即可了解用法。但它有两个致命问题效率低严重依赖数据库层面来搞定复杂的事务绝对不适合高并发的场景规范冲突微服务架构下一个大的系统会拆成几十个甚至几百个服务。规范要求每个服务只能操作自己对应的一个数据库如果允许交叉访问别人的库几百个服务全体乱套数据可能被别人改错、自己的库被别人写挂整个服务无法管理和治理。因此正确做法是要操作别的服务的库必须通过调用别的服务的接口实现绝对不允许交叉访问别人的数据库。也正因如此XA 方案在实际工程中很少用——某个系统内部如果出现跨多个库的操作本身就属于不合规的设计。相关延伸分布式系统为什么必须考虑一致性取舍可参考仓库中 CAP 定理 P 的含义一文——XA 属于典型的强一致性CP 倾向路线以牺牲可用性和性能换取数据一致。TCC 方案Try / Confirm / CancelTCC 的全称是Try、Confirm、Cancel三个阶段Try 阶段对各个服务的资源做检测并对资源进行锁定或者预留。典型操作如冻结资金Confirm 阶段在各个服务中执行实际的操作。Try 阶段所有资源预留成功后进入 Confirm 阶段真正扣款、转账、落库Cancel 阶段如果任何一个服务的业务方法执行出错就需要进行补偿——把那些已经执行成功的业务逻辑回滚掉。图中示例是一个典型的资金场景用户发起操作后系统 A 在 Try 阶段冻结银行 B、银行 C 的资源Confirm 阶段执行插入、扣款、转账一旦失败则进入 Cancel 阶段回滚已执行的操作。优劣势与适用场景优势事务回滚是自动协调的不依赖数据库分布式事务能严格保证分布式事务要么全部成功、要么全部自动回滚劣势事务回滚严重依赖于你自己写代码来回滚和补偿会造成补偿代码巨大、非常难以维护。因此 TCC 的实际使用场景非常聚焦跟钱相关的、支付、交易相关场景严格保证资金的正确性资金上不能出任何问题最好是各个业务执行的时间都比较短避免长时间占用预留资源。但说实话一般尽量别这么搞自己手写回滚/补偿逻辑实在太恶心业务代码很难维护。TCC 最常见的坑是网络问题Try 成功了但 Confirm 请求因网络异常丢失、或 Cancel 补偿请求丢失都需要靠超时重试、幂等接口来兜底这正是面试官常问的TCC 网络连不通怎么办。Saga 方案长事务的补偿模式基本原理金融核心等业务追求强一致性和更高并发量会选择 TCC而金融核心以上的更多业务系统往往选择补偿事务Compensating Transaction。补偿事务处理在 30 多年前就提出了 Saga 理论随着微服务的发展近些年才逐步受到关注。目前业界比较公认的是采用 Saga 作为长事务的解决方案。Saga 的基本原理是业务流程中每个参与者都提交本地事务若某一个参与者失败则补偿前面已经成功的参与者。如上图所示左侧是正常事务流程依次执行 T1、T2、T3……当执行到 T3 时发生错误则开始执行右侧的事务补偿流程反向执行 T3、T2、T1 的补偿服务 C3、C2、C1将 T3、T2、T1 已经修改的数据补偿掉。适用场景对于一致性要求高、短流程、并发高的场景如金融核心系统优先考虑 TCC 方案。而在另一些场景下并不需要这么强的一致性只需要保证最终一致性即可很多金融核心以上的业务渠道层、产品层、系统集成层特点是最终一致即可、流程多、流程长还可能要调用其它公司的服务这类场景如果选 TCC一来开发成本高二来无法要求其它公司的服务也遵循 TCC 模式提供 Try/Confirm/Cancel 三个接口同时流程长、事务边界太长、加锁时间长会严重影响并发性能。因此 Saga 模式的适用场景可以归纳为业务流程长、业务流程多参与者包含其它公司或遗留系统服务无法提供 TCC 模式要求的三个接口。优势与缺点优势一阶段提交本地事务无锁高性能参与者可异步执行高吞吐补偿服务易于实现——一个更新操作的反向操作比如扣减库存的反向是回补库存是比较容易理解的。缺点不保证事务的隔离性。Saga 的各本地事务是逐步提交的中间状态对其它事务可见需要业务侧自行处理脏读问题。本地消息表方案本地消息表是国外 eBay 搞出来的一套思想核心是用本地事务保证消息的可靠性用消息驱动对端异步执行。整体流程如下A 系统在自己本地一个事务里操作业务数据的同时插入一条数据到消息表接着 A 系统将这个消息发送到 MQ 中去B 系统接收到消息之后在一个事务里往自己本地消息表里插入一条数据同时执行其他的业务操作如果这个消息已经被处理过了那么这个事务会回滚从而保证不会重复处理消息B 系统执行成功之后更新自己本地消息表的状态以及 A 系统消息表的状态如果 B 系统处理失败了那么就不更新消息表状态此时 A 系统会定时扫描自己的消息表发现未处理的消息会再次发送到 MQ 中去让 B 再次处理这个方案保证了最终一致性哪怕 B 事务失败A 会不断重发消息直到 B 那边成功为止。从图中可以看到完整的闭环A 系统的数据库里维护业务表 消息表A 通过 MQ 把消息发给 BB 同样以本地事务 消息表带 orderId 唯一约束 幂等判断的方式消费最后通过状态回写与定时扫描保证消息最终都被处理。局限这个方案最大的问题在于严重依赖于数据库的消息表来管理事务高并发场景怎么办怎么扩展消息表本身会成为数据库的瓶颈。所以一般确实很少用。相关延伸B 系统消息已处理过则回滚依赖的就是消息消费幂等性具体做法数据库唯一键、Redis 记录消费 ID 等可参考仓库的如何保证消息不被重复消费一文。可靠消息最终一致性方案这个方案的意思是干脆不要用本地的消息表了直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。整体思路如下A 系统先发送一个prepared 消息到 MQ如果这个 prepared 消息发送失败就直接取消操作别执行了如果消息发送成功了接着执行本地事务成功就告诉 MQ 发送确认消息失败就告诉 MQ回滚消息如果发送了确认消息B 系统会接收到确认消息然后执行本地的事务MQ 会自动定时轮询所有 prepared 消息、回调你的接口问你这个消息是不是本地事务处理失败了——所有没发送确认的消息是继续重试还是回滚一般来说这里可以查数据库看之前本地事务是否执行如果回滚了这里也回滚。这一步是为了避免本地事务执行成功了但确认消息却发送失败了这种情况要是 B 系统的事务失败了怎么办重试自动不断重试直到成功如果实在不行要么针对重要的资金类业务进行回滚B 系统本地回滚后想办法通知 A 也回滚要么发送报警由人工来手工回滚和补偿这个方案目前比较主流国内互联网公司大都是这么玩儿的要么直接用 RocketMQ 支持的事务消息要么自己基于 ActiveMQ、RabbitMQ 封装一套类似的逻辑出来。从图中可以看到A 系统先发 prepared 消息本地事务成功后发 confirm 消息MQ 定时轮询未确认消息B 系统消费消息执行本地事务——这套流程把本地事务与消息发送的原子性问题交给了 MQ 的确认与轮询机制来解决。与本地消息表的本质区别本地消息表把消息记录放在业务库的同库消息表里靠本地事务保证不丢靠定时扫描重发可靠消息方案把未确认消息托管给MQ 中间件由 MQ 负责持久化、定时轮询与状态回调业务系统无需维护消息表扩展性更好。相关延伸方案中反复强调的重试直到成功依赖 MQ 的可靠性传输保障具体到 RabbitMQ 的 confirm/事务机制、Kafka 的 acks 与副本机制可参考仓库的如何保证消息的可靠性传输一文而B 系统重试处理、避免重复生效依赖消费幂等性实现思路唯一键、Redis 标记等参见消息幂等性与接口幂等性设计。最大努力通知方案这个方案的大致意思是系统 A 本地事务执行完之后发送个消息到 MQ这里会有个专门消费 MQ 的最大努力通知服务这个服务会消费 MQ然后写入数据库中记录下来或者放入内存队列也可以接着调用系统 B 的接口要是系统 B 执行成功就 ok 了要是系统 B 执行失败了最大努力通知服务就定时尝试重新调用系统 B反复 N 次最后还是不行就放弃。可以看出最大努力通知是一种尽力而为、不做强保证的最终一致性方案它没有消息表、没有 prepared 消息确认机制只靠消费消息 → 记录 → 定时重试 N 次来尽量把结果通知到对端适合对一致性要求低、允许最终失败由人工兜底的场景例如某些通知类、对账类业务。你们公司是如何处理分布式事务的——面试回答模板如果你真的被问到可以这么说强一致性场景我们某某特别严格的场景用的是TCC来保证强一致性——比如严格资金要求绝对不能错的场景最终一致性场景其他一些场景基于阿里的RocketMQ实现分布式事务——比如订单插入之后要调用库存服务更新库存库存数据没有资金那么敏感就可以用可靠消息最终一致性方案。即选型原则可以概括为资金类强一致用 TCC一般业务最终一致用可靠消息RocketMQ。几点实战提醒RocketMQ 版本注意RocketMQ 3.2.6 之前的版本可以按照上述 prepared/confirm 思路使用事务消息之后接口做了一些改变使用时需以当前版本官方文档的接口为准自研可行如果你愿意完全可以参考可靠消息最终一致性方案的思路基于 RocketMQ 自己实现一套分布式事务——上述第 1~6 步就是完整的自研蓝图兜底机制无论选哪种方案都要考虑重试、幂等与人工补偿报警这三个兜底手段这正是仓库中幂等性与消息可靠性两篇文档反复强调的工程要点。方案对比总览方案一致性强度是否依赖数据库是否依赖 MQ实现成本典型场景XA两阶段提交强一致强依赖资源管理器否低中间件/JTA 支撑单块应用跨多库不适合高并发TCC强一致否否高手写大量补偿代码资金、支付、交易等短流程高一致性场景Saga最终一致否否可异步编排中每步配一个补偿操作长流程、多参与者含外部/遗留系统本地消息表最终一致强依赖业务库内消息表是中消息可靠性要求高、并发量不大的场景可靠消息最终一致性最终一致否强依赖如 RocketMQ中订单、库存等大多数分布式事务场景最大努力通知最终一致尽力而为可选记录用是低对失败容忍度高、允许人工兜底的场景结语分布式事务没有银弹关键在于按业务的一致性要求选型资金强一致走 TCC一般业务走可靠消息最终一致性RocketMQ长流程多参与者走 SagaXA 与本地消息表则更多作为理解原理的参照。面试时能清晰讲出这 6 种方案的原理、优缺点与选型逻辑并联系消息可靠性、幂等性等配套工程手段就足以证明你对分布式事务有体系化的认知。进一步学习可继续阅读仓库中的分布式系统章节以及MQ 可靠性传输、消息幂等性等关联文档。【免费下载链接】advanced-java Core Interview Questions Answers For Experienced Java(Backend) Developers | 互联网 Java 工程师进阶知识完全扫盲涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识项目地址: https://gitcode.com/doocs/advanced-java创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考