分布式事务TCC补偿机制

wen java案例 2

本文目录导读:

分布式事务TCC补偿机制

  1. 核心思想:业务拆分为三个阶段
  2. 执行流程与补偿机制(核心)
  3. TCC的设计难点与“陷阱”
  4. TCC vs. 2PC(两阶段提交)
  5. 最佳实践建议

这是一个非常经典的分布式事务问题,TCC(Try-Confirm-Cancel)是一种补偿型事务模式,通过业务逻辑的拆解来保证分布式系统的最终一致性。

下面我从核心思想、执行流程、补偿机制、设计难点、与2PC的区别、最佳实践这六个方面为你详细解析。


核心思想:业务拆分为三个阶段

TCC 的核心思想是:将一个完整的业务操作,拆分成三个阶段,并为每个阶段预留一个“反操作”或“修复操作”,它不再依赖于数据库的本地事务,而是由业务代码来控制。

  • Try(尝试预留): 检查并预留业务资源,确保后续操作有足够的条件执行。这个阶段通常不会真正提交事务,而是锁定资源。
  • Confirm(确认提交): 如果所有参与者的 Try 阶段都成功,则进入 Confirm 阶段,这个阶段真正执行业务操作,释放 Try 阶段预留的资源。这个阶段的逻辑通常是幂等的
  • Cancel(取消回滚): 如果任何一个参与者的 Try 阶段失败(超时、报错等),则对所有已成功的 Try 操作执行 Cancel 操作,释放预留的资源,回滚到初始状态。

执行流程与补偿机制(核心)

以经典的“跨行转账”为例:A银行扣100元,B银行加100元。

1 正常流程(一路绿灯)

A银行 (扣款)         协调器(TC)            B银行 (加款)
  |                    |                     |
  |---Try(冻结100元)-->|                     |
  |<-Try OK-----------|                     |
  |                    |---Try(请求加款)---->|
  |                    |<--Try OK------------|
  |                    |                     |
  |                    |  (所有Try都成功)   |
  |                    |                     |
  |<-Confirm(扣款100)-|                     |
  |<-Confirm OK-------|                     |
  |                    |--Confirm(加款100)-->
  |                    |<--Confirm OK--------|
  |                    |                     |
  • Try阶段:
    • A:检查账户余额,冻结100元(状态变为“冻结中”,余额不减,可用余额减少)。
    • B:检查账户是否存在,预留加款额度(不是直接加100,而是记录一个“待加款”状态)。
  • Confirm阶段:
    • A:将冻结的100元真正扣除,解除冻结。
    • B:将预留的100元真正加到余额中,消除“待加款”状态。
  • Cancel阶段: (在正常流程中不执行)

2 异常流程与补偿机制(谁出事,谁回滚)

场景:A成功冻结,B因账户异常Try失败。

A银行 (扣款)         协调器(TC)            B银行 (加款)
  |                    |                     |
  |---Try(冻结100元)-->|                     |
  |<-Try OK-----------|                     |
  |                    |---Try(请求加款)---->|
  |                    |<--Try_FAILED--------|   // B报错
  |                    |                     |
  |                    |  (检测到失败)     |
  |                    |                     |
  |<--Cancel(解冻100)--|                     |   // **补偿:A必须回滚**
  |<-Cancel OK---------|                     |
  |                    |                     |
  • 检测到失败: 协调器发现B的Try失败了。
  • 触发补偿: 协调器立即调用A的 Cancel 服务。
  • Cancel逻辑(补偿):
    • A:找到之前冻结的100元记录,将其解冻(恢复可用余额)。
    • 最终结果:A账户资金没动,B账户没变化,系统回滚到初始状态,保证了最终一致性

TCC的设计难点与“陷阱”

TCC看起来很完美,但在实际开发中,这3个方面很容易踩坑:

  1. 空回滚(Empty Rollback):

    • 问题: 如果一个微服务的Try方法因网络问题从未被执行(协调器没收到请求),但协调器却直接调用了它的Cancel方法,此时该服务没有需要回滚的资源,Cancel必须能正确处理空状态。
    • 解决方案: Cancel方法需要判断是否存在Try的记录,如果不存在,则直接返回成功(幂等处理)。
  2. 幂等(Idempotency):

    • 问题: 网络超时、协调器重试等都可能导致Try/Confirm/Cancel被调用多次。
    • 解决方案: 每个请求都必须有全局唯一的事务ID,在资源表中记录事务ID,通过唯一索引或状态机来保证同一个事务ID的操作只生效一次。
  3. 悬挂(Suspension):

    • 问题: Try请求因为网络延迟,在Cancel请求之后才到达,这时Cancel已经完成,Try才执行,导致“冻结”了无法被解冻的资源(永久悬挂)。
    • 解决方案: Cancel方法执行后,记录一个“已取消”状态,当Try请求到达时,检查该状态,如果已取消,则拒绝执行Try并直接返回成功(或抛出异常)。

TCC vs. 2PC(两阶段提交)

很多人容易混淆两者,核心区别在于资源锁定粒度事务控制权

特性 2PC(两阶段提交) TCC(补偿型)
资源锁定 数据库/系统级别(如数据库行锁、全局锁),冲突较高。 业务逻辑级别(通过状态字段、预留表),冲突较低。
事务控制 数据库或中间件控制,属于强一致性(XA协议)。 业务代码控制,属于最终一致性(BASE理论)。
性能 较低,因为要锁定资源直到事务结束,高并发下易死锁。 较高,因为只锁定业务资源,不阻塞数据库。
故障处理 阻塞与超时:协调器宕机,参与者会一直阻塞等着,复杂性极高。 补偿与重试:利用消息队列、重试机制处理,抗风险能力强。
适用场景 短事务、低并发、对一致性要求极高的场景(如金融核心结算)。 长事务、高并发、对性能要求高的场景(如电商下单、积分、支付)。

最佳实践建议

  1. 引入成熟框架: 不要手写TCC,容易出错,推荐使用:

    • Seata TCC模式(阿里):主流方案,与Spring Cloud/ Alibaba集成好。
    • ByteTCC(开源):轻量级。
    • TCC-Transaction(开源):经典方案。
  2. 业务设计原则: 尽量把Try阶段的资源预留设计得轻量级(比如只改状态,不锁大量数据行),而复杂逻辑尽量放在Confirm阶段。

  3. 日志与监控: 所有Try/Confirm/Cancel的调用都必须有完整日志,并记录事务状态,搭建报警系统,监控长时间处于“pending(待处理)”状态的事务。

  4. 重试与补偿策略: 为Confirm和Cancel设置重试机制(如指数退避、基于消息队列的重试),直到业务逻辑成功或达到最大重试次数后触发人工介入。

  5. 业务隔离性: 在Try阶段预留的资源,对其他业务是可见但不可修改的(冻结”状态),这要求在数据库查询余额时,要过滤掉冻结部分。

TCC通过业务逻辑的分解与补偿,用最终一致性换取了高性能高可用,但它需要你手动实现空回滚、幂等、悬挂这三个关键细节,且对业务侵入性较强,如果你的系统并发量不高且对一致性要求极高(比如1ms都不能差),2PC/XA协议可能更合适;但在互联网高并发场景下,TCC通常是更优解。

抱歉,评论功能暂时关闭!