分布式多阶段入参参数获取

在分布式 TCC 模式中,xid(全局事务 ID)和 branchId(分支事务 ID)是 Seata 等框架识别分布式事务的核心标识,而业务参数(如订单、账户对象)的传递 / 获取,需结合框架的 Context 上下文机制实现。

saga是通过反射拿到参数给compensation 回滚方法的

为什么TCC是从上下文取的,原因:

1.Confirm/Cancel 由 Seata TC 直接调用(而非 AOP 拦截),无法捕获 Try 阶段的入参快照

2.TCC 的 Confirm/Cancel 不是像 SAGA 那样 "和正向方法静态绑定",Try/Confirm/Cancel 是 TCC 接口的三个独立方法

3.TCC 的 BranchContext 本质是 "分支事务的参数容器",解决的是 "跨阶段、跨线程、跨调用时机" 的参数传递问题:

①Try 阶段 :调用 tryWithdraw(BranchContext context, ...) 时,业务方将入参(userId/amount)存入 context,Seata 会将 context 序列化后和 branchId 绑定,存储到 TC;

②Confirm/Cancel 阶段 :TC 调用对应分支的 Confirm/Cancel 方法时,会将该分支绑定的 context 反序列化,作为入参传入方法;

③核心优势BranchContext 是 "和分支事务绑定的参数快照",而非 "和线程绑定",能跨时间、跨调用方传递参数 ------ 这是 SAGA 的 "全局入参快照" 做不到的。

总结原因:

TCC 的 Try/Confirm/Cancel 是三个独立、异步、分支级 的方法调用,无法像 SAGA/XA/AT 那样依赖 "方法入参快照 + 反射" 或 "线程本地存储" 传参,只能通过 BranchContext 这种 "和分支事务绑定的上下文容器",将 Try 阶段的入参精准传递到 Confirm/Cancel 阶段

以下以 Seata TCC 为例,讲清「xid/branchId 如何通过 Context 获取」「业务对象如何绑定到 Context」「落地实操步骤」,覆盖核心逻辑和代码示例:

一、核心概念先理清

标识 / 对象 作用
xid 全局事务唯一标识,贯穿整个分布式事务(如 192.168.1.100:8091:123456
branchId 分支事务 ID,每个微服务的 TCC 方法对应一个 branchId,隶属于 xid
BranchContext Seata TCC 专属上下文对象,封装了 xid/branchId + 自定义业务参数
RootContext Seata 全局上下文工具类,用于在线程上下文中存储 / 获取 xid

二、Seata TCC 中通过 Context 获取 xid/branchId + 业务对象

Seata TCC 的 try/confirm/cancel 方法默认会传入 BranchContext 参数,框架已自动填充 xidbranchId;业务对象则需手动绑定到 Context 中,核心分「绑定参数」和「获取参数」两步:

步骤 1:TCC 方法定义(框架自动注入 BranchContext)

Seata 要求 TCC 接口的 try/confirm/cancel 方法第一个参数必须是 BranchContext,框架会自动注入上下文(包含 xid/branchId):

java

运行

复制代码
// TCC 接口(定义 try/confirm/cancel 方法)
public interface AccountTccService {
    // Try 阶段:第一个参数必须是 BranchContext
    boolean tryWithdraw(BranchContext context, Long userId, BigDecimal amount);
    
    // Confirm 阶段:参数和 Try 一致,框架自动传 Context
    boolean confirmWithdraw(BranchContext context, Long userId, BigDecimal amount);
    
    // Cancel 阶段:同理
    boolean cancelWithdraw(BranchContext context, Long userId, BigDecimal amount);
}
步骤 2:获取 Context 中的 xid/branchId(框架已自动填充)

在 TCC 方法实现中,可直接从 BranchContext 获取 xidbranchId,无需手动设置:

java

运行

复制代码
@Service
public class AccountTccServiceImpl implements AccountTccService {
    @Override
    public boolean tryWithdraw(BranchContext context, Long userId, BigDecimal amount) {
        // 1. 从 BranchContext 获取 xid(全局事务 ID)
        String xid = context.getXid();
        // 2. 从 BranchContext 获取 branchId(分支事务 ID)
        long branchId = context.getBranchId();
        
        // 业务逻辑:冻结金额(Try 阶段核心)
        System.out.println("Try 阶段 - xid: " + xid + ", branchId: " + branchId);
        accountMapper.freezeBalance(userId, amount);
        return true;
    }

    @Override
    public boolean confirmWithdraw(BranchContext context, Long userId, BigDecimal amount) {
        // 同理,获取 xid/branchId
        String xid = context.getXid();
        // 业务逻辑:扣减冻结金额(Confirm 阶段核心)
        accountMapper.deductFrozenBalance(userId, amount);
        return true;
    }

    @Override
    public boolean cancelWithdraw(BranchContext context, Long userId, BigDecimal amount) {
        // 同理,获取 xid/branchId
        String xid = context.getXid();
        // 业务逻辑:释放冻结金额(Cancel 阶段核心)
        accountMapper.releaseFrozenBalance(userId, amount);
        return true;
    }
}
步骤 3:传递复杂业务对象(如 OrderDTO)到 Context

如果参数是复杂对象(而非简单的 userId/amount),需手动将对象绑定到 BranchContextargs 中,确保 confirm/cancel 阶段能获取:

java

运行

复制代码
// 发起分布式事务的入口方法(@GlobalTransactional 标记全局事务)
@Service
public class OrderServiceImpl {
    @Autowired
    private AccountTccService accountTccService;
    
    @GlobalTransactional // Seata 全局事务注解,自动生成 xid 并绑定到 RootContext
    public void createOrder(OrderDTO orderDTO) {
        // 1. 构建 BranchContext,绑定业务对象(orderDTO)
        BranchContext context = new BranchContext();
        // 方式1:直接存入自定义参数(推荐,简单对象)
        context.put("orderId", orderDTO.getOrderId());
        context.put("userId", orderDTO.getUserId());
        context.put("amount", orderDTO.getAmount());
        
        // 方式2:序列化复杂对象存入(如 OrderDTO)
        context.put("orderDTO", JSON.toJSONString(orderDTO));
        
        // 2. 调用 TCC 的 Try 方法,传入 Context
        accountTccService.tryWithdraw(context, orderDTO.getUserId(), orderDTO.getAmount());
    }
}

// TCC 实现类中获取复杂对象
@Service
public class AccountTccServiceImpl implements AccountTccService {
    @Override
    public boolean tryWithdraw(BranchContext context, Long userId, BigDecimal amount) {
        // 1. 获取框架自动填充的 xid/branchId
        String xid = context.getXid();
        long branchId = context.getBranchId();
        
        // 2. 获取手动绑定的业务参数
        Long orderId = (Long) context.get("orderId");
        // 3. 获取序列化的复杂对象(如 OrderDTO)
        String orderDTOJson = (String) context.get("orderDTO");
        OrderDTO orderDTO = JSON.parseObject(orderDTOJson, OrderDTO.class);
        
        // 业务逻辑:基于 orderDTO 执行冻结操作
        accountMapper.freezeBalance(orderDTO.getUserId(), orderDTO.getAmount());
        return true;
    }
}
步骤 4:全局上下文 RootContext 获取 xid(补充场景)

如果在 TCC 方法外(如工具类、拦截器)需要获取 xid,可通过 Seata 的 RootContext(线程本地存储):

java

运行

复制代码
// 任意位置获取 xid(需在分布式事务线程内)
String xid = RootContext.getXID();
if (xid != null) {
    System.out.println("当前全局事务 ID:" + xid);
}

// 手动绑定 xid 到线程上下文(极少用,框架自动处理)
RootContext.bind("192.168.1.100:8091:123456");
// 解绑 xid
RootContext.unbind();

三、核心原理:Context 的底层实现

Seata 的 BranchContext 本质是「线程本地(ThreadLocal)存储的 Map 结构」:

  1. xid/branchId 自动填充@GlobalTransactional 注解会触发 Seata 生成 xid,并在调用 TCC 方法时,将 xid/branchId 自动写入 BranchContext
  2. 业务参数手动传递BranchContext 内置 Map<String, Object>,可存储任意自定义参数,本质是 "线程内的参数传递容器";
  3. 跨服务传递:Seata 会将 xid 封装到 RPC 调用的请求头中(如 Dubbo 的 attachment、Spring Cloud 的 Header),保证跨微服务调用时,xid 能传递到下游服务的线程上下文。

四、落地注意事项

  1. 参数序列化:复杂对象(如 OrderDTO)存入 Context 时,需序列化(如 JSON),避免跨服务 / 跨线程时出现 "对象无法序列化" 问题;

  2. 参数一致性try/confirm/cancel 方法的参数需保持一致,或通过 Context 传递相同的核心参数(如 orderId),避免 Confirm/Cancel 阶段参数缺失;

  3. 避免超大参数:Context 存储的参数会随 RPC 传递,避免存入超大对象(如文件、大列表),否则会影响性能;

  4. 线程切换问题 :如果 TCC 方法内有异步操作(如线程池),需手动将 xid 传递到异步线程(Seata 提供 TccResource 工具类支持):

    java

    运行

    复制代码
    // 异步线程中传递 xid
    String xid = RootContext.getXID();
    executorService.submit(() -> {
        RootContext.bind(xid); // 手动绑定 xid 到异步线程
        try {
            // 执行异步业务逻辑
        } finally {
            RootContext.unbind(); // 解绑
        }
    });

五、总结

TCC 模式中通过 Context 获取 xid/branchId 和业务对象的核心逻辑:

  1. 框架自动处理 :xid/branchId 由 Seata 自动注入 BranchContext,无需手动设置;
  2. 手动传递业务参数 :复杂对象通过 BranchContextput/get 方法绑定 / 获取,或序列化后存储;
  3. 全局上下文兜底 :非 TCC 方法内可通过 RootContext 获取 xid,适配特殊场景。
相关推荐
跟着珅聪学java3 小时前
在电商系统中,如何确保库存扣减的原子性
分布式
JH30735 小时前
Redisson 看门狗机制:让分布式锁“活”下去的智能保镖
分布式
一点 内容7 小时前
深入理解分布式共识算法 Raft:从原理到实践
分布式·区块链·共识算法
8Qi87 小时前
分布式锁-redission
java·redis·分布式·redisson
7 小时前
鸿蒙——分布式数据库
数据库·分布式
阿拉斯攀登10 小时前
Spring Cloud Alibaba 生态中 RocketMQ 最佳实践
分布式·微服务·rocketmq·springcloud·cloudalibaba
无锡布里渊10 小时前
感温光纤 DTS 系统 vs 感温电缆 对比分析报告
分布式·实时监测·分布式光纤测温·线型感温火灾监测·感温电缆
g323086311 小时前
分布式框架seata AT模式源码分析
java·数据库·分布式
哇哈哈&11 小时前
如何进行卸载rabbitmq
分布式·rabbitmq