微服务架构下的分布式事务解决方案
第一篇内容:
- 什么是分布式事务(定义、产生背景、核心难点)
- 什么时候使用(跨库、跨服务场景、电商案例)
- 理论基石(CAP 权衡、CP vs AP)
- 强一致性方案(2PC 详解与缺陷、3PC 的改进与局限)
架构视角下的分布式事务(一):核心理论与强一致性方案
第二篇内容:
- TCC 补偿型事务(Try-Confirm-Cancel 详解、三大异常处理)
- 可靠消息最终一致性(基于 MQ + 本地消息表方案)
- Saga 长事务模式(编排式 vs 协同式、ACD 模型)
- 全文总结与选型建议
TCC - 补偿型事务
TCC
TCC是一种位于应用层(Business Layer)的分布式事务解决方案。与 2PC/XA 依赖数据库层面的锁不同,TCC 将资源管理的粒度上移到业务逻辑中,通过业务代码来实现资源的预留、确认和释放。
它本质上是一种柔性事务(Flexible Transaction),遵循BASE 理论,追求最终一致性。可以将其视为**“应用层的两阶段提交”**。
TCC 执行流程
TCC 模式将一个业务操作拆分为三个接口:Try、Confirm、Cancel。
三阶段详解
Try 阶段(业务检查 & 资源预留):
- 核心逻辑:完成所有的业务一致性检查(Business Check),并预留必须的业务资源(Reserve Resources)。
- 特点:只做“准”操作,不做“实”操作。
- 示例:转账场景中,不是直接扣钱,而是检查余额是否充足,并将转账金额从“可用余额”移动到“冻结字段”。
Confirm 阶段(业务确认):
- 核心逻辑:真正执行业务,不做任何业务检查(Try 阶段已做过)。只使用 Try 阶段预留的资源。
- 特点:满足幂等性。如果 Try 成功,Confirm 必须成功。如果执行失败,事务管理器会进行重试,直到成功。
Cancel 阶段(业务取消):
- 核心逻辑:释放 Try 阶段预留的业务资源。
- 特点:满足幂等性。这是“反向操作”或“补偿操作”。
TCC 的三大关键技术挑战
在分布式网络环境下,TCC 必须处理三种棘手的异常情况,这要求业务代码具备极高的鲁棒性。
1. 幂等性
- 场景:无论是 Confirm 还是 Cancel,都可能因为网络超时(Packet Loss)导致事务管理器重复发送请求。
- 要求:业务接口必须保证重复调用产生的结果是一样的。
- 实现:通常通过引入“事务记录表”或利用“状态机”判断,如果该事务 ID 已经处理过,则直接返回成功,不重复扣款或回滚。
2. 空回滚
- 场景:事务管理器调用 Service A 的
Try请求,但因为网络故障,请求未到达Service A,或者 Service A 还没处理完就抛错了。此时事务管理器判定 Try 失败,触发全局回滚,向 Service A 发送Cancel请求。 - 问题:Service A 根本没收到过
Try,却收到了Cancel。 - 要求:
Cancel接口必须能识别这种情况,允许“未 Try 先 Cancel”,直接返回成功,什么都不做。 - 实现:查询事务记录表,如果没找到对应的 Try 记录,说明是空回滚。
3. 资源悬挂
- 场景:事务管理器调用
Try,发生严重的网络拥塞(超时)。事务管理器判定超时,发出Cancel。Cancel先到达并执行成功(处理了空回滚逻辑)。随后,迟到的Try请求终于到达了。 - 问题:如果执行了这个迟到的
Try,资源就被预留(冻结)了。但因为全局事务已经结束(已回滚),永远不会有Confirm或Cancel来释放这笔资源。这笔资源就被永久“悬挂”锁死了。 - 要求:
Try方法执行前,必须检查该事务 ID 是否已经执行过Cancel。如果已回滚,拒绝执行Try。 - 实现:利用“防悬挂记录”或检查事务状态,若发现已有 Cancel 记录,则 Try 抛出异常或直接返回。
TCC 的优缺点深度分析
优点:性能与灵活性
- 高并发(High Concurrency):
- TCC 不使用数据库层面的全局排他锁(Global Lock)。
- 锁的粒度在业务层(如行记录的状态字段),只会阻塞该条数据的特定状态变更,不会阻塞整个表的访问。
- 资源锁定时间短,不占用数据库连接。
- 细粒度控制:
- 可以针对不同的服务实现不同的补偿逻辑,支持异构数据库。
缺点:高开发成本与代码侵入
- 强代码侵入性:
- Service 层的每个接口都必须改造。原本的一个 update 接口,现在需要拆解为 Try、Confirm、Cancel 三个逻辑。
- 业务逻辑与事务逻辑耦合严重。
- 开发成本高昂 :
- 一个业务链路如果有 5 个服务,就需要写 15 个接口方法。
- 开发人员必须深刻理解 TCC 原理,手动处理预留资源的逻辑(如给数据库表加
frozen_amount字段)。
- 复杂度极高:
- 必须完美处理上述的幂等、空回滚、悬挂三大异常,否则会导致数据错乱或资金损失。这通常需要依赖成熟的 TCC 框架(如 Seata TCC 模式, ByteTCC, Himly)来辅助实现。
可靠消息最终一致性
可靠消息最终一致性
可靠消息最终一致性是一种基于BASE 理论的分布式事务解决方案,通常归类为AP(可用性优先)架构。
它通过引入消息队列 (Message Queue, MQ)进行异步解耦,将长事务拆分为本地事务和下游事务。其核心目标是保证:只要上游服务(生产者)的本地事务提交成功,下游服务(消费者)最终一定能收到消息并执行成功。
该方案通常采用“本地消息表 (Transactional Outbox Pattern)”或“事务消息 (Transactional Messaging)”来实现。
架构流程图 (基于本地消息表模式)
此图展示了如何保证“本地事务执行”与“消息发送”的原子性,以及下游如何保证最终成功。
核心机制深度解析
1. 上游原子性:本地消息表
这是解决“业务执行”与“发送MQ”非原子性问题的经典方案。
- 问题背景:先发 MQ 后写库,库写失败了怎么办?先写库后发 MQ,MQ 发失败了怎么办?
- 解决方案:
- 利用本地数据库的ACID 特性。
- 在同一个本地事务中,既写入业务数据(如:生成订单),也写入一条消息记录(状态:待发送)。
- 结果:业务数据和消息记录要么同时存在,要么同时回滚。
2. 消息投递:可靠性保证
即使消息落库了,还需要保证投递到 MQ。
- 轮询/投递 (Poller):一个独立的线程或定时任务不断扫描“待发送”的消息,投递到 MQ。
- 状态翻转:投递成功后,将本地消息状态更新为**“已发送”**。
- 兜底机制:如果更新状态失败,或者 MQ 没收到,定时任务下次还会扫描到这条消息进行重发(这就要求下游必须幂等)。
3. 下游消费:重试与死信
下游服务可能因为网络抖动、数据库繁忙等原因暂时失败。
- ACK 机制 (Acknowledgement):只有业务逻辑完全执行成功后,消费者才向 MQ 发送确认(ACK)。
- 自动重试策略:如果消费者抛出异常或超时未 ACK,MQ 会根据策略(如指数退避算法:1s, 5s, 10s…)重新投递消息。
- 死信队列 (Dead Letter Queue, DLQ):当重试次数达到阈值(如 16 次)仍未成功,MQ 将该消息移入 DLQ。这意味着发生了人工介入级别的故障(如代码 Bug 或数据错误),系统会自动告警,由运维或开发手动处理。
4. 关键防御:幂等性设计 (Idempotency)
由于网络的不确定性和 MQ 的重试机制,消息被重复投递是必然发生的 (At-Least-Once Delivery)。下游必须保证重复消费不会导致业务错误。
- 唯一键约束 (Unique Key):利用数据库的唯一索引(如
order_id或message_id)防止插入重复记录。 - 去重表 (Deduplication Table):专门建立一张表记录已处理的
message_id。消费前先查表,若存在则直接 ACK 并返回。 - 状态机控制 (State Machine):
UPDATE orders SET status = 'PAID' WHERE id = 100 AND status = 'UNPAID';- 利用
UPDATE语句的行锁和条件判断(CAS 乐观锁思想),确保状态只能流转一次。
方案优缺点总结
优点:
- 解耦 (Decoupling):生产者不需要关心消费者是否在线,发完消息即可结束。
- 削峰填谷 (Load Leveling):能够承受高并发冲击。
- 代码侵入性较低:相比 TCC,不需要拆分 Try/Confirm/Cancel,主要逻辑集中在消息发送和消费端。
缺点:
- 实时性较差:依赖异步投递,存在一定延迟。
- 运维复杂度:需要维护独立的消息服务、定时任务和消息表清理机制。
- 不支持回滚:一旦消息发出,如果下游一直失败(进入死信),上游通常无法自动回滚,只能通过“人工补偿”或生成“反向消息”来修正数据。
Saga模式 - 长事务补偿
Saga 模式
Saga 模式是一种处理分布式系统中长运行事务(Long-Running Transaction)的解决方案。它放弃了传统 ACID 中强一致性和隔离性的要求,转而采用BASE 理论。
Saga 将一个全局的分布式事务拆分为一系列有序的本地事务 (Local Transaction)。每个本地事务更新数据库并发布事件触发下一个本地事务。
核心逻辑包含两个部分:
- 正向操作 (Transaction, T):正常的业务逻辑执行。
- 补偿操作 (Compensation, C):当某个步骤失败时,用于撤销之前操作的逻辑。
Saga 的两种主要实现模式
编排式 (Orchestration):
- 引入一个中心化的SEC (Saga Execution Coordinator)。
- 协调器负责指挥:“A 执行完告诉我,我再通知 B;如果 B 失败,我通知 A 执行补偿”。
- 特点:可视化流畅,状态机逻辑清晰,便于排查问题。
协同式 (Choreography):
- 没有中心协调器,服务之间通过事件 (Event)驱动。
- 服务 A 完成后发消息,服务 B 监听消息并执行。
- 特点:耦合度极低,在业务流程混乱时难以追踪。
编排式 (Orchestration)是目前更主流、更推荐的选择
Saga 执行流程图 (基于编排器模式)
下图展示了 Saga 在遇到故障时,如何通过反向补偿来回滚事务。
假设业务流程是:扣减库存 (T1)->创建订单 (T2)->支付扣款 (T3)。
场景:T1, T2 成功,但 T3 失败。
流程详解:
- 正向执行:依次执行 T1, T2。每个事务执行完立即提交本地数据库事务,锁资源被释放。
- 故障触发:执行到 T3 时发生错误(如余额不足)。
- 反向补偿:Saga 协调器(Orchestrator)感知到 T3 失败,开始按相反顺序调用已完成步骤的补偿操作。
- 调用
C2(T2 的补偿):将订单状态改为“已取消”或物理删除。 - 调用
C1(T1 的补偿):将扣减的库存加回去。
- 调用
- 最终状态:数据库回滚到事务开始前的状态(数据语义上的一致)。
核心特性与缺陷深度解析
1. 缺失隔离性 - ACD 模型
这是 Saga 与 TCC/XA 最大的区别,也是其被诟病为“ACD”(缺了 I - Isolation)的原因。
- 现象:在 Saga 执行过程中(例如 T1 刚提交,T2 还没开始),其他并发的业务请求可以看到T1 已经提交的数据。
- 风险 - 脏读 (Dirty Read):如果 T1 扣了库存,此时用户 B 查询到了有库存。但随后 Saga 触发了补偿 C1 把库存加回去了。用户 B 看到的就是一个中间状态的数据。
- 对策:业务设计上必须容忍“语义锁”或使用“冻结字段”策略,或者在业务层面做隔离(例如订单状态标记为“处理中”,对用户不可见)。
2.补偿的复杂性
“撤销”往往比“做”更难。
- 逻辑难写:发送邮件的 T 操作,其 C 操作无法“撤回邮件”,只能“发送一封更正邮件”。
- 重试机制:补偿操作
C1本身也可能失败(例如网络断了)。Saga 框架必须保证补偿操作无限重试直到成功(这就要求人工介入死信队列作为最后的兜底)。
3. 必须幂等
由于网络环境的不稳定性,Saga 协调器可能会重复触发正向操作(T)或补偿操作(C)。
- 要求:
C1执行一次和执行 N 次,效果必须完全一样(例如:库存 = 库存 + 1,必须改为:如果流水号不存在则库存+1)。
总结
Saga 是一种**“先提交,后补偿”的模式。
它牺牲了隔离性(Isolation),换取了长事务场景下的高并发与低延迟**(因为没有长周期的数据库锁)。适用于业务流程长、跨系统多、且允许短暂数据不一致的场景。
全文总结与选型指南
分布式事务是微服务架构中绕不开的挑战。从早期的数据库层面的2PC/XA,到后来应用层面的TCC、Saga,技术演进的核心逻辑始终是在“一致性 (Consistency)”与“可用性/性能 (Availability/Performance)”之间寻找平衡点。
1. 技术演进脉络
- 从刚性到柔性:传统的2PC/3PC追求 ACID 的强一致性,但因同步阻塞导致性能低下,不适应互联网高并发场景。因此,基于 BASE 理论的柔性事务(TCC、MQ、Saga)应运而生,它们允许数据在短时间内不一致,但保证最终一致。
- 从数据库层到业务层:事务管理的控制权逐渐从数据库(DB Lock)上移至业务代码(Business Logic)。虽然这增加了开发的复杂度(如处理幂等、空回滚),但也带来了极大的性能提升和灵活性。
2. 核心选型
在做架构决策时,可以参考以下优先级:
首选策略:避免分布式事务
- 如果可以通过合理的微服务拆分(领域模型调整)或数据分片策略,将操作限制在同一个数据库内,利用本地事务解决,那是最高效的。
资金/核心业务:强一致性 (Seata AT / 2PC)
- 场景:银行转账、企业内部 ERP、数据准确性要求极高的核心链路。
- 理由:必须保证数据时刻一致,不能容忍中间状态,且并发量通常可控。
高并发互联网业务:最终一致性 (MQ / TCC)
- 场景 1 (高并发、解耦):电商下单后发货、积分发放、推送通知。
- 推荐:可靠消息最终一致性 (MQ)。实现解耦,削峰填谷。
- 场景 2 (强实时、细粒度控制):支付扣款、库存扣减(需立即反馈结果)。
- 推荐:TCC。虽然代码侵入大,但能提供最高的性能和控制力。
- 场景 1 (高并发、解耦):电商下单后发货、积分发放、推送通知。
长链路复杂业务:Saga 模式
- 场景:旅游预订(机票+酒店+租车)、跨境物流链。
- 推荐:Saga (编排式)。流程长、参与者多,不需要强隔离性,侧重于流程的可视化管理和补偿机制。