分布式事务深度解析:从理论到实践

分布式事物

一、背景:从用户下单场景看分布式事务痛点

  • 在电商场景中,用户下单涉及多个关键操作:
    • 1)账户服务扣减余额
    • 2)库存服务扣减库存
    • 3)订单服务生成订单记录
    • 这三个操作需要强一致性保证 ,必须保持原子性------要么全部成功,要么全部回滚。若其中一个操作失败(如库存不足),必须保证所有操作回滚,否则会导致超卖或用户资金异常
    • 传统单体应用可以通过数据库事务保证,但在微服务架构下,这三个服务使用独立的数据库,形成典型的分布式事务难题。
java 复制代码
// 伪代码示例:存在数据不一致风险的原始代码
public void createOrder(OrderDTO order) {
    // 扣减库存(库存服务)
    stockFeignClient.deduct(order.getSkuId(), order.getNum());
    
    // 扣减余额(账户服务)
    accountFeignClient.deduct(order.getUserId(), order.getMoney());
    
    // 创建订单(订单服务)
    orderMapper.create(order);
}

关键矛盾点

  • 网络抖动可能导致部分服务调用失败
  • 服务宕机可能造成"半提交"状态
  • 传统单机事务无法跨数据库保证原子性

二、Seata架构核心原理

java 复制代码
// 全局事务启动示例

    @GlobalTransactional
    public void purchase(String userId, String commodityCode, int orderCount) {
        ......
    }

三、分布式事物模型

  • 1、子系统之间必须能感知到彼此的事物状态,才能保证状态一致
  • 2、需要事物协调者TC(Transaction Coordinator) 来协调每一个事物的参与者(子系统事物)
  • 3、子系统事物,称为分支事物 ,关联各个分支事物在一起称为全局事物

四、名词解释

  • 全局事物整个分布式事物
  • 分支事物分布式事物中包含的每个子系统事物
  • 最终一致性 :各分支事物分别执行并提交,如果有不一致的情况,想办法补偿恢复,达到数据最终一致性
  • 强一致性:各事务执行完业务不要提交,等待彼此结束,之后统一提交或回滚

五、Seata分布式事务架构

事务管理三大核心组件协同

  • TC(Transaction Coordinator):事物协调者维护全局和分支事物的状态,协调全局事物提交或回滚
  • TM(Transaction Manager):事物管理者定义全局事物的范围、开始全局事物、提交或者回滚全局事物。
  • RM(Resource Manager):资源管理者管理分支事物处理的资源,与TC(事物协调者)交谈以注册分支事物和报告分支事物的状态,并驱动分支事物提交或回滚。
组件 角色 典型实现
TC 事务协调者 Seata Server
TM 事务管理器 @GlobalTransactional注解
RM 资源管理器 数据源代理

Seata分布式事务的基本模型的执行流程

  • 1、TM(Transaction Manager 事务管理者) 会首先注册全局事务
  • 2、之后业务调用各个微服务 ,由各自的RM(Resource Manager 资源管理者)TC(Transaction Coordinator 事务协调者) 发起分支事务的注册
  • 3、之后执行各个分支事务的sql ,执行完毕之后RM(Resource Manager 资源管理者) 会向TC(Transaction Manager 事务管理者) 报告分支事务的状态
  • 4、所有分支事务执行完毕之后TM(事务管理者)TC(事务协调者) 发起提交或回滚全局事务
  • 5、此时TC(事务协调者)检查分支事务的状态决定是提交还是回滚发送给RM(资源管理者)

Seata四种不同分布式事物解决方案

  • XA模式强一致性分阶段事物模式 ,牺牲了一定的可用性无业务侵入。一般由数据库实现

    • XA模式原理 :XA规范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准 ,XA规范描述了全局的TM(事务管理者) 与局部的RM(资源管理者) 之间的接口,几乎所有主流的数据库都对XA规范提供了支持。
    • 第一阶段事物协调者(TC)资源管理者(RM)发起事物准备请求资源管理者(RM)执行完毕之后,并不直接提交事务,而是将执行的结果告知事物协调者(TC)
    • 第二阶段事物协调者(TC)判断资源管理者(RM)的返回结果,如果分支事物都成功了,向资源管理者(RM)发起提交请求,资源管理者(RM)执行事物并返回已提交请求
    • 如果事物执行过程中有一个失败了,事物协调者会回滚所有已执行事物。
    • Seata的XA模式实现原理
      • 资源管理者(RM)一阶段工作
        • 1、注册分支事务到事务协调者(TC).
        • 2、执行分支事务SQL但不提交.
        • 3、报告执行状态到事务协调者(TC).
      • 事务协调者(TC)一阶段工作
        • 1、TC检测各分支事物执行状态
          • 都成功,通知所有资源管理器(RM)提交事务
          • 有失败,通知所有资源管理器(RM)回滚事务
      • 资源管理者(RM)二阶段工作
        • 接受事物协调者(TC)指令,提交或回滚事物。
    • XA模式总结
      • 优点:
        • 事物强一致性,满足ACID原则
        • 常用数据库都支持,实现简单,没有代码侵入
      • 缺点:
        • 一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
        • 依赖关系型数据库实现事务
sql 复制代码
/* 数据库层XA命令示例 */
XA START 'xid1';
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
XA END 'xid1';
XA PREPARE 'xid1';
XA COMMIT 'xid1';

适用场景传统金融系统政府业务等一致性要求极高的场景


  • TCC模式:最终一致的分阶段事务模式,有业务侵入。

    • TCC模式原理与AT模式非常相似,每阶段都是独立事物 ,不同的是TCC通过人工编码实现数据恢复
    • 需实现的三个方法
      • Try(检查):资源的检测跟预留。
      • Confirm(提交):完成资源操作业务。要求Try和Confirm一定要能成功。
      • Cancel(回滚):预留资源释放,可以理解为Try的反向操作。
    • 举例:一个扣减用户余额业务。账户A原有余额100,需要扣减30元。
      • 一阶段(Try):检查余额是否充足,如充足则冻结金额加30元,用户余额扣减30元。
      • 二阶段(Confirm):如需提交,冻结金额扣减30元。
      • 三阶段(Cancel):如需回滚,冻结金额扣减30元,可用余额增加30元。
    • TCC模式总结
      • 每个阶段是做啥?
        • Try:资源检查跟预留
        • Confirm:业务执行和提交
        • Cancel:预留资源的释放
      • 优点:
        • 一阶段完成直接提交事务,释放数据库资源,性能好。
        • 相比AT模型,无需生成快照,无需使用全局锁,性能最强。
        • 不依赖数据库事物,而是依赖补偿操作,可以用于非事务性数据库。
      • 缺点:
        • 有代码侵入,需要人为编写Try、Confirm和Cancel接口。
        • 软状态,事物是最终一致。
        • 需要考虑Confirm和Cancel失败的情况,做好幂等处理。
java 复制代码
// TCC接口定义示例
public interface AccountTccService {
    
    @TwoPhaseBusinessAction(name = "deduct", 
        commitMethod = "confirm", 
        rollbackMethod = "cancel")
    boolean deduct(BusinessActionContext context,
                   @BusinessActionContextParameter(paramName = "userId") Long userId,
                   @BusinessActionContextParameter(paramName = "money") BigDecimal money);

    boolean confirm(BusinessActionContext context);
    
    boolean cancel(BusinessActionContext context);
}

// Try阶段实现
public boolean deduct(...) {
    // 检查余额
    // 冻结资金(生成冻结记录)
}

// Cancel补偿示例
public boolean cancel(BusinessActionContext context) {
    Long userId = context.getActionContext("userId");
    BigDecimal money = context.getActionContext("money");
    // 解冻资金 + 返还余额
}

开发建议

  1. 做好空回滚幂等处理
  2. 冻结记录表需要包含xid等事务信息
  3. 设置合理的重试机制

  • AT模式:最终一致的分阶段事物模式,无业务侵入,也是Seata默认模式

    • AT模式原理
      • 同样是分阶段提交的事物模型,但弥补了XA模型中资源锁定周期过长的缺陷。
      • 执行完SQL 之后会直接提交事务,而不是进行等待。
      • 在执行的同时资源管理器(RM)拦截本次执行,记录更新前后的快照到数据库的undo_log中。
    • Seata的AT模式实现原理
      • 资源管理者(RM)一阶段工作
        • 1、注册 分支事物
        • 2、记录 undo_log(数据快照)
        • 3、执行 业务SQL并提交。
        • 4、报告 事务状态
      • 资源管理器(RM)二阶段提交的工作
        • 删除undo_log即可。
      • 资源管理器(RM)二阶段回滚的工作
        • 根据undo_log恢复数据到更新前。
    • AT模式总结,与XA模式最大区别是
      • XA模式一阶段不提交事物,锁定资源。AT模式一阶段直接提交,不锁定资源
      • XA模式依赖数据库机制实现回滚,AT模式利用数据快照实现数据回滚
      • XA模式强一致。AT模式最终一致
      • 优点:
        • 1、一阶段完成直接提交事务,释放数据库资源,性能比较好。
        • 2、利用全局锁实现读写隔离。
        • 3、没有代码侵入,框架自动完成回滚跟提交。
      • 缺点:
        • 1、两阶段之间属于软状态,属于最终一致。
        • 2、框架快照功能会影响性能,但比XA模式好很多。
sql 复制代码
-- undo_log表示例结构
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
);

执行过程示例

java 复制代码
// 原始业务代码无需修改
public void deduct(Long userId, BigDecimal money) {
    accountMapper.deduct(userId, money);
}

// MyBatis Mapper
@Update("UPDATE account SET balance = balance - #{money} WHERE user_id = #{userId}")
void deduct(@Param("userId") Long userId, @Param("money") BigDecimal money);

注意事项

  1. 需要开启数据源代理
  2. 避免全局锁冲突导致的性能问题
  3. 快照存储可能带来额外存储开销

  • SAGA模式:长事务模式,有业务侵入。
    • SAGA原理 :业务流程中每个参与者提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者 ,一阶段正向服务和二阶段补偿服务都由业务开发实现。

    • 两个阶段:

      • 一阶段直接提交本地事务(TCC是预留)
      • 二阶段成功则什么都不做,失败则通过编写补偿业务来回滚。
    • 优点:

      • 1、事物参与者可以基于事件驱动实现异步调用,吞吐高
      • 2、一阶段直接提交事务,无锁,性能好
      • 3、不用编写TCC的三个阶段,实现简单。
    • 缺点:

      • 软状态持续时间不确定,时效性差
      • 没有锁,没有事物隔离,会有脏写
java 复制代码
// 状态机配置示例
StateMachineBuilder<State, Event> builder = StateMachineBuilderFactory.create();
builder.configureStates()
    .withStates()
        .initial(State.INIT)
        .state(State.INVENTORY_DEDUCT)
        .state(State.ACCOUNT_DEDUCT)
        .state(State.ORDER_CREATE);

builder.configureTransitions()
    .withExternal()
        .source(State.INIT).target(State.INVENTORY_DEDUCT)
        .event(Event.DEDUCT_INVENTORY)
        .action(deductInventoryAction)
    .withExternal()
        .source(State.INVENTORY_DEDUCT).target(State.ACCOUNT_DEDUCT)
        .event(Event.DEDUCT_ACCOUNT)
        .action(deductAccountAction)
    .withExternal()
        .source(State.ACCOUNT_DEDUCT).target(State.ORDER_CREATE)
        .event(Event.CREATE_ORDER)
        .action(createOrderAction)
    .withExternal()
        .source(State.ORDER_CREATE).target(State.SUCCESS)
        .event(Event.SUCCESS);

// 补偿动作示例
builder.configureTransitions()
    .withExternal()
        .source(State.INVENTORY_DEDUCT).target(State.FAILURE)
        .event(Event.FAILURE)
        .action(compensateInventoryAction);

最佳实践

  1. 为每个事务步骤定义明确的补偿操作
  2. 设计幂等补偿接口
  3. 采用事件驱动架构提升吞吐量

六、四种模式对比

维度\模式 XA TCC AT SAGA
一致性 强一致 弱一致 弱一致 最终一致
隔离性 完全隔离 基于资源预留隔离 基于全局锁隔离 无隔离
侵入性 有,需要编写3个接口 有,需要编写状态机和补偿业务
数据库要求 关系型 任意 关系型 任意
性能
适用场景 对一致性、隔离性有高要求的业务,短事务 对性能要求较高的事务;有非关系型数据库要参与的事务,核心交易 基于关系型数据库的大多数分布式事务场景都可以,普通交易 业务流程长、业务流程多;参与者包含其它公司或遗留系统服务,无法提供TCC模式要求的三个接口,长事务
典型响应时间 100ms+ 50ms+ 80ms+ 200ms+
开发复杂度

七、实战总结

  1. 黄金法则 :能不用分布式事务就不用,优先考虑业务拆分
  2. 降级策略 :在事务执行失败提供友好提示人工干预通道
  3. 监控要点
    • 事务成功率
    • 平均处理时间
    • 资源锁竞争情况
    • 死锁检测
yml 复制代码
// 生产环境配置示例(Seata 1.5+)
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848

避坑指南

  1. 避免在事务中处理RPC文件操作
  2. 合理设置事务超时时间(建议3-8秒)
  3. 对补偿操作进行压力测试
  4. 做好分布式事务日志的归档分析

合理选择事务模式并配合完善的监控体系,可以有效构建高可靠的分布式系统。从AT模式入手,在特殊场景下结合TCC/SAGA方案,形成完整的事务解决方案。

借鉴:cloud.tencent.com/developer/a...

相关推荐
codingandsleeping3 小时前
浏览器的缓存机制
前端·后端
潘多编程3 小时前
SpringBoot分布式项目订单管理实战:Mybatis最佳实践全解
spring boot·分布式·mybatis
追逐时光者3 小时前
面试官问:你知道 C# 单例模式有哪几种常用的实现方式?
后端·.net
Asthenia04124 小时前
Numpy:数组生成/modf/sum/输出格式规则
后端
Asthenia04124 小时前
NumPy:数组加法/数组比较/数组重塑/数组切片
后端
Asthenia04124 小时前
Numpy:limspace/arange/数组基本属性分析
后端
Asthenia04124 小时前
Java中线程暂停的分析与JVM和Linux的协作流程
后端
Asthenia04124 小时前
Seata TCC 模式:RootContext与TCC专属的BusinessActionContext与TCC注解详解
后端
自珍JAVA4 小时前
【代码】zip压缩文件密码暴力破解
后端