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

0 阅读11分钟

分布式事物

分布式事物.png

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

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

关键矛盾点

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

二、Seata架构核心原理

image.png

// 全局事务启动示例

    @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(资源管理者)

image.png

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

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

    • XA模式原理:XA规范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA规范描述了全局的TM(事务管理者) 与局部的RM(资源管理者) 之间的接口,几乎所有主流的数据库都对XA规范提供了支持。

    image.png

    • 第一阶段事物协调者(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原则
        • 常用数据库都支持,实现简单,没有代码侵入
      • 缺点:
        • 一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
        • 依赖关系型数据库实现事务
/* 数据库层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通过人工编码实现数据恢复

    image.png

    • 需实现的三个方法
      • 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失败的情况,做好幂等处理。
// 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中。

    image.png

    • 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模式好很多。
-- 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`)
);

执行过程示例

// 原始业务代码无需修改
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原理:业务流程中每个参与者提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。 image.png

    • 两个阶段:

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

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

      • 软状态持续时间不确定,时效性差
      • 没有锁,没有事物隔离,会有脏写
// 状态机配置示例
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. 采用事件驱动架构提升吞吐量

六、四种模式对比

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

七、实战总结

  1. 黄金法则:能不用分布式事务就不用,优先考虑业务拆分
  2. 降级策略:在事务执行失败提供友好提示人工干预通道
  3. 监控要点
    • 事务成功率
    • 平均处理时间
    • 资源锁竞争情况
    • 死锁检测
// 生产环境配置示例(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…