电商返利场景下的资金池模型:分布式事务 Seata AT 模式与 TCC 模式权衡

大家好,我是 微赚淘客系统3.0 的研发者省赚客!

在返利系统中,用户完成订单后需执行 资金池扣减(平台账户)→ 个人余额增加(用户账户)→ 佣金记录写入 三步操作。由于涉及多个微服务(fund-serviceaccount-servicerebate-record-service),必须保证数据一致性。我们基于 Seata 实现分布式事务,并在 AT 模式TCC 模式 之间进行深度权衡与落地。

一、业务模型与一致性要求

  • 资金池表(fund_pool):记录平台可分配佣金总额;
  • 用户账户表(user_account):记录用户可提现余额;
  • 返利记录表(rebate_record):记录每笔返利明细。

核心约束:

  1. 资金池余额 ≥ 0;
  2. 用户余额变更必须与记录一致;
  3. 不允许重复发放同一笔返利。

二、AT 模式实现:简单但存在脏读风险

AT 模式通过 全局锁 + undo log 自动回滚。

服务调用链

package juwatech.cn.rebate.service;

@Service
public class RebateDistributeService {

    @GlobalTransactional(timeoutMills = 30000)
    public void distributeRebate(RebateRequest request) {
        // 1. 扣减资金池
        fundClient.decreaseFundPool(request.getTradeId(), request.getAmount());
        
        // 2. 增加用户余额
        accountClient.increaseBalance(request.getUserId(), request.getAmount());
        
        // 3. 记录返利
        rebateRecordService.createRecord(request);
    }
}

资金池服务(AT 模式)

package juwatech.cn.fund.service;

@Service
public class FundPoolService {

    @Transactional
    public void decreaseFundPool(String tradeId, BigDecimal amount) {
        // 先查再更新(存在并发超扣风险)
        FundPool pool = fundPoolMapper.selectForUpdate(); // 加行锁
        if (pool.getAvailable().compareTo(amount) < 0) {
            throw new InsufficientFundException();
        }
        fundPoolMapper.decreaseAvailable(amount);
        
        // Seata 自动生成 before image / after image
    }
}

问题

  • selectForUpdate 导致高并发下数据库锁竞争;
  • 全局事务期间,其他读请求可能读到中间状态(如余额已加但记录未写);
  • 无法支持幂等性校验(重复请求会重复扣款)。

三、TCC 模式实现:强一致性但开发复杂

TCC 通过 Try-Confirm-Cancel 三阶段手动控制资源。

定义 TCC 接口

package juwatech.cn.fund.api;

@LocalTCC
public interface FundPoolTccService {

    @TwoPhaseBusinessAction(name = "decreaseFundPoolTcc", commitMethod = "confirm", rollbackMethod = "cancel")
    boolean tryDecrease(@BusinessActionContextParameter(paramName = "tradeId") String tradeId,
                        @BusinessActionContextParameter(paramName = "amount") BigDecimal amount);

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

Try 阶段:冻结资金

@Override
public boolean tryDecrease(String tradeId, BigDecimal amount) {
    // 幂等检查:防止重复 Try
    if (fundFreezeRecordMapper.existsByTradeId(tradeId)) {
        return true; // 已冻结,直接成功
    }

    // 冻结资金(可用余额 - amount,冻结余额 + amount)
    int updated = fundPoolMapper.freezeAmount(amount);
    if (updated == 0) {
        throw new InsufficientFundException();
    }

    // 记录冻结流水
    freezeRecordMapper.insert(new FreezeRecord(tradeId, amount));
    return true;
}

对应 SQL:

UPDATE fund_pool 
SET available = available - #{amount}, 
    frozen = frozen + #{amount} 
WHERE available >= #{amount};

Confirm 阶段:扣除冻结

@Override
public boolean confirm(BusinessActionContext context) {
    String tradeId = context.getActionContext("tradeId", String.class);
    FreezeRecord record = freezeRecordMapper.getByTradeId(tradeId);
    if (record != null && !record.isConfirmed()) {
        fundPoolMapper.confirmDeduct(record.getAmount()); // frozen -= amount
        freezeRecordMapper.markConfirmed(tradeId);
    }
    return true;
}

Cancel 阶段:解冻

@Override
public boolean cancel(BusinessActionContext context) {
    String tradeId = context.getActionContext("tradeId", String.class);
    FreezeRecord record = freezeRecordMapper.getByTradeId(tradeId);
    if (record != null && !record.isCancelled()) {
        fundPoolMapper.unfreeze(record.getAmount()); // available += amount, frozen -= amount
        freezeRecordMapper.markCancelled(tradeId);
    }
    return true;
}

用户账户服务同理实现 TCC 接口。

主业务调用

@GlobalTransactional
public void distributeRebateTcc(RebateRequest request) {
    fundPoolTccService.tryDecrease(request.getTradeId(), request.getAmount());
    userAccountTccService.tryIncrease(request.getUserId(), request.getAmount());
    rebateRecordService.createRecord(request); // 本地事务,无需 TCC
}

四、AT 与 TCC 对比

维度 AT 模式 TCC 模式
开发成本 低(几乎无侵入) 高(需实现 3 个方法)
性能 中(全局锁 + undo log) 高(无长事务,仅冻结)
一致性 最终一致(存在脏读窗口) 强一致(业务层控制)
幂等性 需额外处理 Try 阶段天然支持
适用场景 低并发、非核心资金 高并发、资金核心链路

五、生产选型结论

  • 资金池扣减、用户余额变更:采用 TCC 模式,确保资金安全;
  • 返利记录写入、通知发送:采用 AT 模式 或异步最终一致;
  • 防重设计:所有入口基于 trade_id 做幂等拦截,避免重复触发。

通过 TCC 模式,系统在大促期间处理 8 万+ 返利事务/分钟,资金零差错。

本文著作权归 微赚淘客系统3.0 研发团队,转载请注明出处!

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐