微服务分布式事务解决方案
核心思想:从强一致性转向最终一致性
微服务架构更倾向于拥抱最终一致性,通过补偿、异步、重试等机制保证数据最终正确,牺牲强一致性换取系统的可用性、分区容忍性和扩展性。
🔄 主流解决方案
-
两阶段提交
-
原理: 协调者协调所有参与者执行一个两阶段协议。
-
准备阶段: 协调者询问所有参与者是否可以提交事务。参与者执行事务操作(但不提交),锁定资源,并回复 YES/NO。
-
提交/回滚阶段: 如果所有参与者回复 YES,协调者发送提交指令,参与者提交事务并释放锁;如果任一参与者回复 NO 或超时,协调者发送回滚指令,参与者回滚事务并释放锁。
-
-
优点: 理论上是强一致性方案。
-
缺点:
-
同步阻塞: 参与者在准备阶段锁定资源,直到收到第二阶段指令,影响并发性能。
-
协调者单点故障: 协调者宕机可能导致参与者资源长期锁定。
-
数据不一致风险: 在第二阶段,如果协调者或网络故障,部分参与者可能提交成功,部分可能未收到指令而回滚或保持阻塞(需人工干预)。
-
性能开销: 网络通信次数多,延迟高。
-
-
适用场景: 对强一致性要求极高且性能要求不高、参与者较少的内部系统(如银行内部转账)。现代分布式系统较少直接采用原生 2PC。
-
代表实现: XA 协议(如 JTA/JDBC 支持)、Seata AT 模式(内部基于类似2PC思想但做了优化)。
-
-
补偿事务 / Saga 模式
-
原理: 将一个长事务拆分成一系列本地事务,每个本地事务对应一个服务操作。每个本地事务完成后,发布事件或消息触发下一个服务操作。关键: 为每个正向操作定义一个对应的、幂等的补偿操作。
-
执行顺序:
-
成功: T1 (成功) -> T2 (成功) -> T3 (成功) -> ... -> 完成
-
失败: T1 (成功) -> T2 (成功) -> T3 (失败) -> C3 (补偿) -> C2 (补偿) -> C1 (补偿) -> 回滚完成
-
-
-
实现方式:
-
协同式 Saga: 由协调器(一个专门的 Saga 执行服务)按预定义顺序调用各个服务,并在失败时调用补偿服务。逻辑集中。
-
编排式 Saga: 服务之间通过事件/消息驱动。每个服务执行完本地事务后发布事件,触发下一个服务执行。补偿逻辑也通过事件触发。逻辑分散。
-
-
优点:
-
无全局锁: 参与者只在执行本地事务时持有本地资源锁,释放快,并发高。
-
松耦合: 服务之间通过事件通信,耦合度低。
-
灵活性高: 流程定义灵活,支持复杂业务流程。
-
-
缺点:
-
设计复杂: 需要仔细设计正向流程和补偿流程,确保补偿的幂等性。
-
最终一致性: 非强一致,存在中间状态。
-
补偿失败处理: 补偿操作本身也可能失败,需要额外机制(重试、人工干预)。
-
调试困难: 分布式链路跟踪至关重要。
-
-
适用场景: 长业务流程、跨多个服务的业务活动(如电商下单:扣库存、创建订单、扣减积分)。
-
代表实现: Axon Framework, Eventuate Tram, Camunda, Seata Saga 模式。
-
-
TCC 模式
-
原理: 将业务操作分为三个阶段:
-
Try: 尝试执行业务,完成所有业务检查,并预留必要的业务资源(如冻结库存、预扣金额)。该阶段操作需幂等。
-
Confirm: 确认执行业务。真正使用 Try 阶段预留的资源。该操作需幂等且保证成功(通常只需简单更新状态)。
-
Cancel: 取消执行业务。释放 Try 阶段预留的资源。该操作需幂等。
-
-
流程:
-
成功: 所有参与者 Try 成功 -> 协调者调用所有参与者的 Confirm。
-
失败: 任一参与者 Try 失败 -> 协调者调用所有已 Try 成功参与者的 Cancel(进行回滚)。
-
-
优点:
-
最终一致性: 比 2PC 更灵活。
-
无全局锁: 只在 Try 阶段锁定预留资源,Confirm/Cancel 执行很快,资源锁定时间相对较短。
-
较高并发: 相比 2PC。
-
-
缺点:
-
开发复杂: 每个服务需要实现 Try/Confirm/Cancel 三个接口,业务侵入性强。
-
业务模型约束: 业务需能分解出明确的“预留资源”概念。
-
空回滚/悬挂问题: 需处理因网络问题导致的 Try 未执行但收到 Cancel(空回滚),或 Try 超时后 Confirm/Cancel 才到达(悬挂)等问题,通常通过事务状态记录和幂等设计解决。
-
Confirm/Cancel 失败: 需重试保证最终成功。
-
-
适用场景: 对一致性要求较高、业务能清晰拆分为 Try/Confirm/Cancel 三阶段、且性能要求也较高的场景(如资金交易)。
-
代表实现: Seata TCC 模式, ByteTCC, tcc-transaction.
-
-
基于本地消息表
-
原理:
-
业务服务在本地事务中执行操作并同时向本地数据库的消息表插入一条待发送的消息(状态为“待发送”)。本地事务保证业务操作和消息写入的原子性。
-
后台定时任务轮询消息表,将“待发送”状态的消息发送到消息队列。
-
消息发送成功后,将消息状态更新为“已发送”(或删除)。
-
下游服务消费消息队列中的消息,执行业务操作。
-
下游服务处理成功后,可以发送ACK确认;如果处理失败或超时未确认,消息队列会重投消息(需保证消费逻辑幂等)。
-
-
优点:
-
简单可靠: 实现相对简单,依赖成熟的 MQ。
-
最终一致性: 利用 MQ 的重试机制保证消息最终被消费。
-
业务侵入性较低: 主要在生产者端增加写消息表的逻辑。
-
-
缺点:
-
消息表耦合: 业务数据库需维护消息表。
-
延迟: 依赖于轮询或定时任务发送消息,有一定延迟。
-
消息顺序问题: MQ 不严格保证顺序时,下游处理需考虑乱序影响。
-
严格依赖MQ: MQ 的可用性和可靠性直接影响方案。
-
-
适用场景: 对实时性要求不高、需要最终一致性的跨服务操作(如积分增减、通知发送)。
-
代表实现: RocketMQ 事务消息(原理类似但优化了消息表)、自研方案。
-
-
事务消息
-
原理: 消息队列提供的一种特殊消息类型(如 RocketMQ 的事务消息、Kafka 需要外部协调)。
-
Half Message: 生产者向 Broker 发送一条“半消息”(对消费者不可见)。
-
执行本地事务: 生产者执行本地业务逻辑。
-
提交/回滚:
-
本地事务成功:生产者通知 Broker 提交事务消息,消息变为对消费者可见。
-
本地事务失败:生产者通知 Broker 回滚事务消息,消息被删除。
-
-
Broker 回查: 如果生产者未响应提交/回滚状态(如崩溃),Broker 会主动回调生产者的一个接口查询该消息的最终状态。
-
-
优点:
-
解耦消息表: 无需业务方维护本地消息表,由 MQ 中间件保证事务语义。
-
最终一致性: 消息最终可靠投递。
-
-
缺点:
-
依赖MQ支持: 需要消息队列支持事务消息功能(如 RocketMQ)。
-
回查逻辑: 需要实现状态回查接口,保证幂等。
-
消费者需幂等: 同本地消息表。
-
-
适用场景: 同本地消息表,且使用支持事务消息的 MQ(如 RocketMQ)。
-
代表实现: Apache RocketMQ。
-
📊 方案选择决策因素
-
一致性要求:
-
必须强一致:考虑 2PC(需承受其缺点)或 TCC(侵入性强)。
-
接受最终一致:Saga、本地消息表、事务消息、TCC 均可,根据其他因素选择。
-
-
业务复杂度和流程长度:
-
简单短流程:TCC、本地消息表、事务消息。
-
复杂长流程:Saga(尤其是编排式)更合适。
-
-
性能要求:
-
高并发低延迟:避免 2PC,优先 Saga、TCC、消息方案。
-
-
业务侵入性与开发成本:
-
低侵入:本地消息表、事务消息。
-
高侵入:TCC(需实现3接口)、Saga(需设计补偿逻辑)。
-
-
技术栈和基础设施:
-
是否有支持事务消息的 MQ(RocketMQ)?
-
是否已采用特定框架(Seata)?
-
-
团队熟悉度:
-
选择团队理解更深入、能驾驭的方案。
-
🧩 总结表格
实践建议
-
尽量避免分布式事务: 通过设计(如 API 组合、领域事件、CQRS)尽量减少跨服务的写操作。
-
优先考虑最终一致性: 绝大多数业务场景最终一致性是可接受的。
-
Saga 是通用选择: 对于复杂的业务流程,编排式 Saga 通常是平衡性较好的选择。
-
TCC 用于高一致性要求: 对资金、库存等核心资源操作要求较高一致性且能拆分三阶段时选用。
-
消息方案用于解耦和异步: 对实时性要求不高、需要解耦的场景,本地消息表或事务消息(尤其是 RocketMQ)是常用方案。
-
幂等性是基石: 在最终一致性方案中,服务接口(尤其是补偿操作、消息消费)的幂等性设计至关重要。
-
完善监控和告警: 分布式事务状态、消息积压、补偿失败等都需要完善监控和告警,以便及时发现问题。
-
考虑人工干预兜底: 设计异常处理流程和人工干预入口,应对自动化机制无法解决的极端情况(如补偿多次失败)。
选择哪种方案取决于你的具体业务场景、技术栈、团队能力和一致性要求。理解每种方案的原理、约束和适用场景,是做出合适技术选型的关键。 分布式事务没有“银弹”,通常需要结合多种模式来解决系统中的不同问题。💡