基于 TCC 模式实现普通下单场景的分布式一致性方案

方案背景与适用场景

在分布式系统中,跨服务操作(如 “下单 + 扣库存”)的一致性保障是核心难题。TCC(Try-Confirm-Cancel)作为柔性事务的典型实现,通过 “预留资源 - 确认提交 - 取消回滚” 三阶段操作,能在不依赖强一致性中间件的前提下,实现分布式场景下的最终一致性。

本文分享的 TCC 方案,专门针对非秒杀、非热点商品的普通下单场景—— 该场景下流量相对平稳,无需应对高并发下的极端性能挑战,更侧重事务的可靠性与易用性,同时通过事务日志记录、幂等处理、异常补偿等设计,解决了 TCC 模式中常见的空回滚、悬挂、重复执行等问题。

核心设计与代码结构

方案整体基于 Java 语言开发,依赖 MyBatis-Plus 实现数据持久化、Dubbo 实现跨服务调用,核心分为 “通用 TCC 事务框架” 与 “下单业务实现” 两部分,结构清晰且可复用。

1.通用 TCC 事务框架

负责处理 TCC 三阶段的事务状态管理、幂等控制,是所有 TCC 业务的基础支撑,核心组件如下:

组件类型 核心类 / 接口 功能说明
配置类 TccConfiguration 扫描 TCC 相关 Mapper,注入 TransactionLogService(事务日志服务)
实体类 TransactionLog 事务日志核心实体,记录事务 ID、业务场景、业务模块、事务状态、回滚类型,是状态判断的核心依据
枚举类 TransActionLogState 定义 TCC 三阶段状态:TRY(资源预留)、CONFIRM(确认提交)、CANCEL(取消回滚)
枚举类 TransTry/Cancel/ConfirmSuccessType 定义各阶段的执行结果(如 TRY_SUCCESS首次成功、DUPLICATED_TRY幂等成功),解决重复调用问题
服务类 TransactionLogService 实现 TCC 三阶段核心逻辑:-tryTransaction:记录 TRY 状态日志,实现资源预留的幂等- confirmTransaction:将状态更新为 CONFIRM,确认提交- cancelTransaction:处理回滚(含空回滚、幂等回滚场景)

2. 普通下单业务实现

基于通用 TCC 框架,实现 “创建订单 + 扣减库存” 的跨服务事务,核心流程与代码如下:

(1)下单核心流程

遵循 TCC 三阶段逻辑,同时增加异常补偿机制,确保事务最终一致:

  1. Try 阶段:调用库存服务冻结库存、订单服务创建待确认订单,通过 TransactionLog记录 TRY 状态;
  2. Confirm 阶段:若 Try 全部成功,重试调用确认接口(最多 2 次),将库存从 “冻结” 转为 “扣减”、订单状态改为 “已确认”;
  3. Cancel 阶段:若 Try/Confirm 失败,发送补偿消息,触发库存解冻、订单作废,通过 TransactionLog避免重复回滚。
(2)关键业务代码
  • 下单入口(TradeApplicationService):串联 TCC 三阶段,处理异常与补偿消息发送;
  • 库存服务实现(GoodsTransactionFacadeServiceImpl):基于 TransactionLogService,实现库存的 “冻结(Try)、扣减(Confirm)、解冻(Cancel)”,并通过分布式锁避免并发问题;
  • 订单服务实现:类似库存服务,实现订单的 “创建待确认(Try)、确认(Confirm)、作废(Cancel)”。

方案核心亮点

  1. 解决 TCC 典型问题
    • 幂等性:通过 TransactionLog判断历史执行记录,避免重复 Try/Confirm/Cancel;
    • 空回滚:若 Cancel 先于 Try 执行,直接记录 EMPTY_CANCEL状态,防止无资源可回滚;
    • 悬挂问题:通过事务日志状态校验,避免 Try 在 Cancel 后执行导致的资源不一致。
  2. 高可靠性
    • Confirm 阶段增加重试机制(最多 2 次),应对服务临时不可用;
    • 失败场景发送延迟补偿消息,通过监听机制触发二次校验,避免 “疑似失败” 场景。
  3. 易用性与可复用
    • 通用 TCC 框架与业务解耦,新增 TCC 事务时只需实现对应业务逻辑(如后续新增 “支付” 环节,无需修改框架);
    • 通过 businessScene(业务场景)、businessModule(业务模块)字段,支持多场景复用。

config

/**
 * @author mifuRD
 */
@Configuration
@MapperScan("cn.mifu.nft.turbo.tcc.mapper")
public class TccConfiguration {

    @Bean
    public TransactionLogService transactionLogService() {
        return new TransactionLogService();
    }
}

entity

/**
 * @author mifuRD
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TransactionLog extends BaseEntity {

    /**
     * 事务ID
     */
    private String transactionId;

    /**
     * 业务场景
     */
    private String businessScene;

    /**
     * 业务模块
     */
    private String businessModule;

    /**
     * 状态
     */
    private TransActionLogState state;

    /**
     * Cancel的类型
     */
    private TransCancelSuccessType cancelType;

    public TransactionLog(TccRequest tccRequest, TransActionLogState state) {
        this.state = state;
        this.transactionId = tccRequest.getTransactionId();
        this.businessScene = tccRequest.getBusinessScene();
        this.businessModule = tccRequest.getBusinessModule();
    }

    public TransactionLog(TccRequest tccRequest, TransActionLogState state, TransCancelSuccessType cancelType) {
        this.state = state;
        this.transactionId = tccRequest.getTransactionId();
        this.businessScene = tccRequest.getBusinessScene();
        this.businessModule = tccRequest.getBusinessModule();
        this.cancelType = cancelType;
    }
}
/**
 * @author mifuRD
 */
public enum TransActionLogState {

    /**
     * Try
     */
    TRY,

    /**
     * Confirm
     */
    CONFIRM,

    /**
     * Cancel
     */
    CANCEL
}
/**
 * @author mifuRD
 */
public enum TransCancelSuccessType {

    /**
     * 回滚成功-TRY-CANCEL
     */
    CANCEL_AFTER_TRY_SUCCESS,

    /**
     * 回滚成功-TRY-CONFIRM-CANCEL
     */
    CANCEL_AFTER_CONFIRM_SUCCESS,

    /**
     * 空回滚
     */
    EMPTY_CANCEL,

    /**
     * 幂等成功
     */
    DUPLICATED_CANCEL;
}
/**
 * @author mifuRD
 */
public enum TransConfirmSuccessType {

    /**
     * Confirm成功
     */
    CONFIRM_SUCCESS,

    /**
     * 幂等成功
     */
    DUPLICATED_CONFIRM;
}
/**
 * @author mifuRD
 */
public enum TransTrySuccessType {

    /**
     * Try成功
     */
    TRY_SUCCESS,

    /**
     * 幂等成功
     */
    DUPLICATED_TRY;
}

mapper

/**
 * @author mifuRD
 * 事务日志
 */
@Mapper
public interface TransactionLogMapper extends BaseMapper<TransactionLog> {

}

request

/**
 * @author mifuRD
 */
@AllArgsConstructor
@NoArgsConstructor
public class TccRequest implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 事务ID
     */
    private String transactionId;

    /**
     * 业务场景
     */
    private String businessScene;

    /**
     * 业务模块
     */
    private String businessModule;

    public String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public String getBusinessScene() {
        return businessScene;
    }

    public void setBusinessScene(String businessScene) {
        this.businessScene = businessScene;
    }

    public String getBusinessModule() {
        return businessModule;
    }

    public void setBusinessModule(String businessModule) {
        this.businessModule = businessModule;
    }
}

response

/**
 * @author mifuRD
 */
@AllArgsConstructor
@NoArgsConstructor
public class TccRequest implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 事务ID
     */
    private String transactionId;

    /**
     * 业务场景
     */
    private String businessScene;

    /**
     * 业务模块
     */
    private String businessModule;

    public String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public String getBusinessScene() {
        return businessScene;
    }

    public void setBusinessScene(String businessScene) {
        this.businessScene = businessScene;
    }

    public String getBusinessModule() {
        return businessModule;
    }

    public void setBusinessModule(String businessModule) {
        this.businessModule = businessModule;
    }
}
/**
 * @author mifuRD
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TransactionConfirmResponse implements Serializable {

    private static final long serialVersionUID = 1L;

    private Boolean success;

    private String errorCode;

    private String errorMsg;

    private TransConfirmSuccessType transConfirmSuccessType;

    public TransactionConfirmResponse(Boolean success, TransConfirmSuccessType transConfirmSuccessType) {
        this.success = success;
        this.transConfirmSuccessType = transConfirmSuccessType;
    }

    public TransactionConfirmResponse(Boolean success, String errorCode, String errorMsg) {
        this.success = success;
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
}
/**
 * @author mifuRD
 */
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class TransactionTryResponse implements Serializable {

    private static final long serialVersionUID = 1L;

    private Boolean success;

    private String errorCode;

    private String errorMsg;

    private TransTrySuccessType transTrySuccessType;

    public TransactionTryResponse(Boolean success, TransTrySuccessType transTrySuccessType) {
        this.success = success;
        this.transTrySuccessType = transTrySuccessType;
    }

    public TransactionTryResponse(Boolean success, String errorCode, String errorMsg) {
        this.success = success;
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
}

自动装配

在META-INF下创建文件,SpringBoot3版本是org.springframework.boot.autoconfigure.AutoConfiguration.imports,2.x版本我记得是spring.factory,至于为什么要改成名字这么长的文件,我记得好像和云原生相关

cn.mifu.nft.turbo.tcc.config.TccConfiguration

service

/**
 * @author mifuRD
 */
public class TransactionLogService extends ServiceImpl<TransactionLogMapper, TransactionLog> {

    /**
     * TCC事务的Try
     *
     * @param tccRequest
     * @return
     */
    public TransactionTryResponse tryTransaction(TccRequest tccRequest) {
        TransactionLog existTransactionLog = getExistTransLog(tccRequest);
        if (existTransactionLog == null) {
            TransactionLog transactionLog = new TransactionLog(tccRequest, TransActionLogState.TRY);
            if (this.save(transactionLog)) {
                return new TransactionTryResponse(true, TransTrySuccessType.TRY_SUCCESS);
            }
            return new TransactionTryResponse(false, "TRY_FAILED", "TRY_FAILED");
        }

        //幂等
        return new TransactionTryResponse(true, TransTrySuccessType.DUPLICATED_TRY);
    }

    /**
     * TCC事务的Confirm
     *
     * @param tccRequest
     * @return
     */
    public TransactionConfirmResponse confirmTransaction(TccRequest tccRequest) {
        TransactionLog existTransactionLog = getExistTransLog(tccRequest);
        if (existTransactionLog == null) {
            throw new UnsupportedOperationException("transacton can not confirm");
        }

        if (existTransactionLog.getState() == TransActionLogState.TRY) {
            existTransactionLog.setState(TransActionLogState.CONFIRM);
            if (this.updateById(existTransactionLog)) {
                return new TransactionConfirmResponse(true, TransConfirmSuccessType.CONFIRM_SUCCESS);
            }

            return new TransactionConfirmResponse(false, "CONFIRM_FAILED", "CONFIRM_FAILED");
        }

        //幂等
        if (existTransactionLog.getState() == TransActionLogState.CONFIRM) {
            return new TransactionConfirmResponse(true, TransConfirmSuccessType.DUPLICATED_CONFIRM);
        }

        throw new UnsupportedOperationException("transacton can not confirm :" + existTransactionLog.getState());
    }

    /**
     * TCC事务的Cancel
     *
     * @param tccRequest
     * @return
     */
    public TransactionCancelResponse cancelTransaction(TccRequest tccRequest) {
        TransactionLog existTransactionLog = getExistTransLog(tccRequest);
        //如果还没有Try,则直接记录一条状态为Cancel的数据,避免发生空回滚,并解决悬挂问题
        if (existTransactionLog == null) {
            TransactionLog transactionLog = new TransactionLog(tccRequest, TransActionLogState.CANCEL, TransCancelSuccessType.EMPTY_CANCEL);
            if (this.save(transactionLog)) {
                return new TransactionCancelResponse(true, TransCancelSuccessType.EMPTY_CANCEL);
            }
            return new TransactionCancelResponse(false, "EMPTY_CANCEL_FAILED", "EMPTY_CANCEL_FAILED");
        }

        if (existTransactionLog.getState() == TransActionLogState.TRY) {
            existTransactionLog.setState(TransActionLogState.CANCEL);
            existTransactionLog.setCancelType(TransCancelSuccessType.CANCEL_AFTER_TRY_SUCCESS);
            if (this.updateById(existTransactionLog)) {
                return new TransactionCancelResponse(true, TransCancelSuccessType.CANCEL_AFTER_TRY_SUCCESS);
            }
            return new TransactionCancelResponse(false, "CANCEL_FAILED", "CANCEL_FAILED");
        }

        if (existTransactionLog.getState() == TransActionLogState.CONFIRM) {
            existTransactionLog.setState(TransActionLogState.CANCEL);
            existTransactionLog.setCancelType(TransCancelSuccessType.CANCEL_AFTER_CONFIRM_SUCCESS);
            if (this.updateById(existTransactionLog)) {
                return new TransactionCancelResponse(true, TransCancelSuccessType.CANCEL_AFTER_CONFIRM_SUCCESS);
            }
            return new TransactionCancelResponse(false, "CANCEL_FAILED", "CANCEL_FAILED");
        }

        //幂等
        if (existTransactionLog.getState() == TransActionLogState.CANCEL) {
            return new TransactionCancelResponse(true, TransCancelSuccessType.DUPLICATED_CANCEL);
        }

        return new TransactionCancelResponse(false, "CANCEL_FAILED", "CANCEL_FAILED");
    }

    private TransactionLog getExistTransLog(TccRequest request) {
        QueryWrapper<TransactionLog> queryWrapper = new QueryWrapper<TransactionLog>();
        queryWrapper.eq("transaction_id", request.getTransactionId());
        queryWrapper.eq("business_scene", request.getBusinessScene());
        queryWrapper.eq("business_module", request.getBusinessModule());

        return this.getOne(queryWrapper);
    }

}

场景

controller

    /**
     * 普通下单,非热点商品,非秒杀场景
     *
     * @param
     * @return 订单号
     */
    @PostMapping("/normalBuy")
    public Result<String> normalBuy(@Valid @RequestBody BuyParam buyParam) {
        OrderCreateAndConfirmRequest orderCreateAndConfirmRequest = getOrderCreateAndConfirmRequest(buyParam);

        OrderResponse orderResponse = RemoteCallWrapper.call(req -> tradeApplicationService.normalBuy(req), orderCreateAndConfirmRequest, "createOrder");

        if (orderResponse.getSuccess()) {
            //同步写redis,如果失败,不阻塞流程,靠binlog同步保障
            try {
                InventoryRequest inventoryRequest = new InventoryRequest(orderCreateAndConfirmRequest);
                inventoryFacadeService.decrease(inventoryRequest);
            } catch (Exception e) {
                log.error("decrease inventory from redis failed", e);
            }

            return Result.success(orderCreateAndConfirmRequest.getOrderId());
        }

        throw new TradeException(TradeErrorCode.ORDER_CREATE_FAILED);
    }

库存tcc

/**
 * @author mifuRD
 */
@Service
@Slf4j
public class TradeApplicationService {

    private static final int MAX_RETRY_TIMES = 2;

    @Autowired
    private OrderTransactionFacadeService orderTransactionFacadeService;

    @Autowired
    private GoodsTransactionFacadeService goodsTransactionFacadeService;

    @Autowired
    private StreamProducer streamProducer;

    /**
     * 普通交易,基于TCC实现分布式一致性
     * <p>
     * Try -> Confirm :Try成功,执行Confirm
     * Try --> Cancel : Try失败,执行Cancel
     * Try -> Confirm --> Cancel :Try成功,Confirm失败,执行Cancel
     *
     * @param orderCreateRequest
     * @returnc
     */
    public OrderResponse normalBuy(OrderCreateAndConfirmRequest orderCreateRequest) {

        boolean isTrySuccess = true;

        //Try
        try {
            GoodsSaleRequest goodsSaleRequest = new GoodsSaleRequest(orderCreateRequest);
            boolean result = goodsTransactionFacadeService.tryDecreaseInventory(goodsSaleRequest).getSuccess();
            Assert.isTrue(result, "decrease inventory failed");

            result = orderTransactionFacadeService.tryOrder(orderCreateRequest).getSuccess();
            Assert.isTrue(result, "order create failed");
        } catch (Exception e) {
            isTrySuccess = false;
            log.error("normalBuy try failed, ", e);
        }

        //Try失败,发【废单消息】,异步进行逆向补偿
        if (!isTrySuccess) {
            //消息监听: NormalBuyMsgListener
            streamProducer.send("normalBuyCancel-out-0", orderCreateRequest.getGoodsType().name(), JSON.toJSONString(orderCreateRequest));
            return new OrderResponse.OrderResponseBuilder().buildFail(NORMAL_BUY_TCC_CANCEL_FAILED.getCode(), NORMAL_BUY_TCC_CANCEL_FAILED.getMessage());
        }

        //Confirm
        boolean isConfirmSuccess = false;
        int retryConfirmCount = 0;

        //最大努力执行,失败最多尝试2次.(Dubbo也会有重试机制,在服务突然不可用、超时等情况下会重试2次)
        while (!isConfirmSuccess && retryConfirmCount < MAX_RETRY_TIMES) {
            try {
                GoodsSaleRequest goodsSaleRequest = new GoodsSaleRequest(orderCreateRequest);
                isConfirmSuccess = goodsTransactionFacadeService.confirmDecreaseInventory(goodsSaleRequest).getSuccess();
                Assert.isTrue(isConfirmSuccess, "confirmDecreaseInventory failed");

                OrderConfirmRequest orderConfirmRequest = new OrderConfirmRequest();
                BeanUtils.copyProperties(orderCreateRequest, orderConfirmRequest);
                isConfirmSuccess = orderTransactionFacadeService.confirmOrder(orderConfirmRequest).getSuccess();
                Assert.isTrue(isConfirmSuccess, "confirmOrder failed");
            } catch (Exception e) {
                retryConfirmCount++;
                isConfirmSuccess = false;
                log.error("normalBuy confirm failed, ", e);
            }
        }

        //Confirm失败,发【疑似废单消息】进行延迟检查
        if (!isConfirmSuccess) {
            //消息监听: NormalBuyMsgListener
            streamProducer.send("normalBuyPreCancel-out-0", orderCreateRequest.getGoodsType().name(), JSON.toJSONString(orderCreateRequest), DELAY_LEVEL_1_M);
            return new OrderResponse.OrderResponseBuilder().buildFail(NORMAL_BUY_TCC_CONFIRM_FAILED.getCode(), NORMAL_BUY_TCC_CONFIRM_FAILED.getMessage());
        }

        return new OrderResponse.OrderResponseBuilder().orderId(orderCreateRequest.getOrderId()).buildSuccess();
    }
}

库存扣减这块涉及一些核心功能,暂不公开展示(还有一套TCC的校验,不过TCC这个方案目前被废弃了,可以先看看TCC这套)

/**
 * @author mifuRD
 */
public interface GoodsTransactionFacadeService {

    /**
     * 锁定库存
     * @param request
     * @return
     */
    public GoodsSaleResponse tryDecreaseInventory(GoodsSaleRequest request);

    /**
     * 解锁并扣减库存
     * @param request
     * @return
     */
    public GoodsSaleResponse confirmDecreaseInventory(GoodsSaleRequest request);

    /**
     * 解锁库存
     * @param request
     * @return
     */
    public GoodsSaleResponse cancelDecreaseInventory(GoodsSaleRequest request);
}
/**
 * @author mifuRD
 */
@DubboService(version = "1.0.0")
public class GoodsTransactionFacadeServiceImpl implements GoodsTransactionFacadeService {

    private static final String ERROR_CODE_UNSUPPORTED_GOODS_TYPE = "UNSUPPORTED_GOODS_TYPE";

    @Autowired
    private CollectionService collectionService;

    @Autowired
    private BlindBoxService blindBoxService;

    @Autowired
    private TransactionLogService transactionLogService;


    @Override
    @Facade
    @Transactional(rollbackFor = Exception.class)
    @DistributeLock(keyExpression = "#request.bizNo",scene = "NORMAL_BUY_GOODS")
    public GoodsSaleResponse tryDecreaseInventory(GoodsSaleRequest request) {

        GoodsFreezeInventoryRequest goodsTrySaleRequest = new GoodsFreezeInventoryRequest(request.getBizNo(), request.getGoodsId(), request.getQuantity());

        GoodsType goodsType = GoodsType.valueOf(request.getGoodsType());

        TransactionTryResponse transactionTryResponse = transactionLogService.tryTransaction(new TccRequest(request.getBizNo(), "normalBuy", goodsType.name()));
        Assert.isTrue(transactionTryResponse.getSuccess(), "transaction try failed");

        if (transactionTryResponse.getTransTrySuccessType() == TransTrySuccessType.TRY_SUCCESS) {
            Boolean freezeResult = switch (goodsType) {
                case BLIND_BOX -> blindBoxService.freezeInventory(goodsTrySaleRequest);
                case COLLECTION -> collectionService.freezeInventory(goodsTrySaleRequest);
                default -> throw new UnsupportedOperationException(ERROR_CODE_UNSUPPORTED_GOODS_TYPE);
            };
            Assert.isTrue(freezeResult, "freeze inventory failed");
            GoodsSaleResponse response = new GoodsSaleResponse();
            response.setSuccess(true);
            return response;
        }

        return new GoodsSaleResponse.GoodsResponseBuilder().buildSuccess();
    }

    @Override
    @Facade
    @Transactional(rollbackFor = Exception.class)
    @DistributeLock(keyExpression = "#request.bizNo",scene = "NORMAL_BUY_GOODS")
    public GoodsSaleResponse confirmDecreaseInventory(GoodsSaleRequest request) {
        GoodsUnfreezeAndSaleRequest unfreezeAndSaleRequest = new GoodsUnfreezeAndSaleRequest(request.getBizNo(), request.getGoodsId(), request.getQuantity());
        GoodsType goodsType = GoodsType.valueOf(request.getGoodsType());
        TransactionConfirmResponse transactionConfirmResponse = transactionLogService.confirmTransaction(new TccRequest(request.getBizNo(), "normalBuy", goodsType.name()));
        Assert.isTrue(transactionConfirmResponse.getSuccess(), "transaction confirm failed");

        if (transactionConfirmResponse.getTransConfirmSuccessType() == TransConfirmSuccessType.CONFIRM_SUCCESS) {
            Boolean unfreezeResult = switch (goodsType) {
                case BLIND_BOX -> blindBoxService.unfreezeAndSale(unfreezeAndSaleRequest);
                case COLLECTION -> collectionService.unfreezeAndSale(unfreezeAndSaleRequest);
                default -> throw new UnsupportedOperationException(ERROR_CODE_UNSUPPORTED_GOODS_TYPE);
            };
            Assert.isTrue(unfreezeResult, "unfreeze inventory failed");

            GoodsSaleResponse response = new GoodsSaleResponse();
            response.setSuccess(true);
            return response;
        }

        return new GoodsSaleResponse.GoodsResponseBuilder().buildSuccess();
    }

    @Override
    @Facade
    @Transactional(rollbackFor = Exception.class)
    @DistributeLock(keyExpression = "#request.bizNo",scene = "NORMAL_BUY_GOODS")
    public GoodsSaleResponse cancelDecreaseInventory(GoodsSaleRequest request) {
        GoodsType goodsType = GoodsType.valueOf(request.getGoodsType());
        TransactionCancelResponse transactionCancelResponse = transactionLogService.cancelTransaction(new TccRequest(request.getBizNo(), "normalBuy", goodsType.name()));
        Assert.isTrue(transactionCancelResponse.getSuccess(), "transaction cancel failed");

        //如果发生空回滚,或者回滚幂等,则不进行解冻库存操作
        //Try成功后的Cancel,直接解冻库存
        if (transactionCancelResponse.getTransCancelSuccessType() == TransCancelSuccessType.CANCEL_AFTER_TRY_SUCCESS) {
            GoodsUnfreezeInventoryRequest unfreezeInventoryRequest = new GoodsUnfreezeInventoryRequest(request.getBizNo(), request.getGoodsId(), request.getQuantity());
            Boolean unfreezeResult = switch (goodsType) {
                case BLIND_BOX -> blindBoxService.unfreezeInventory(unfreezeInventoryRequest);
                case COLLECTION -> collectionService.unfreezeInventory(unfreezeInventoryRequest);
                default -> throw new UnsupportedOperationException(ERROR_CODE_UNSUPPORTED_GOODS_TYPE);
            };
            Assert.isTrue(unfreezeResult, "unfreeze inventory failed");
        }

        //如果发生空回滚,或者回滚幂等,则不进行解冻库存操作
        //Confirm成功后的Cancel,直接回滚库存
        if (transactionCancelResponse.getTransCancelSuccessType() == TransCancelSuccessType.CANCEL_AFTER_CONFIRM_SUCCESS) {
            GoodsCancelSaleRequest goodsCancelSaleRequest = new GoodsCancelSaleRequest(request.getBizNo(), request.getGoodsId(), request.getQuantity());
            Boolean cancelResult = switch (goodsType) {
                case BLIND_BOX -> blindBoxService.cancel(goodsCancelSaleRequest);
                case COLLECTION -> collectionService.cancel(goodsCancelSaleRequest);
                default -> throw new UnsupportedOperationException(ERROR_CODE_UNSUPPORTED_GOODS_TYPE);
            };
            Assert.isTrue(cancelResult, "cancel inventory failed");
        }

        GoodsSaleResponse response = new GoodsSaleResponse();
        response.setSuccess(true);
        return response;
    }
}

订单tcc

/**
 * 订单事务门面服务
 *
 * @author mifuRD
 */
public interface OrderTransactionFacadeService {

    /**
     * 创建订单
     *
     * @param orderCreateRequest
     * @return
     */
    public OrderResponse tryOrder(OrderCreateRequest orderCreateRequest);

    /**
     * 确认订单
     *
     * @param orderConfirmRequest
     * @return
     */
    public OrderResponse confirmOrder(OrderConfirmRequest orderConfirmRequest);

    /**
     * 撤销订单
     *
     * @param orderDiscardRequest
     * @return
     */
    public OrderResponse cancelOrder(OrderDiscardRequest orderDiscardRequest);
}

/**
 * @author mifuRD
 */
@DubboService(version = "1.0.0")
public class OrderTransactionFacadeServiceImpl implements OrderTransactionFacadeService {

    @Autowired
    private OrderManageService orderManageService;

    @Autowired
    private TransactionLogService transactionLogService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Facade
    @DistributeLock(keyExpression = "#orderCreateRequest.orderId",scene = "NORMAL_BUY_ORDER")
    public OrderResponse tryOrder(OrderCreateRequest orderCreateRequest) {
        TransactionTryResponse transactionTryResponse = transactionLogService.tryTransaction(new TccRequest(orderCreateRequest.getOrderId(), "normalBuy", "ORDER"));
        Assert.isTrue(transactionTryResponse.getSuccess(), "transaction try failed");

        if (transactionTryResponse.getTransTrySuccessType() == TransTrySuccessType.TRY_SUCCESS) {
            OrderResponse orderResponse = orderManageService.create(orderCreateRequest);
            Assert.isTrue(orderResponse.getSuccess(), () -> new BizException(OrderErrorCode.CREATE_ORDER_FAILED));
            return orderResponse;
        }

        return new OrderResponse.OrderResponseBuilder().buildSuccess();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Facade
    @DistributeLock(keyExpression = "#orderConfirmRequest.orderId",scene = "NORMAL_BUY_ORDER")
    public OrderResponse confirmOrder(OrderConfirmRequest orderConfirmRequest) {
        TransactionConfirmResponse transactionConfirmResponse = transactionLogService.confirmTransaction(new TccRequest(orderConfirmRequest.getOrderId(), "normalBuy", "ORDER"));
        Assert.isTrue(transactionConfirmResponse.getSuccess(), "transaction confirm failed");

        if(transactionConfirmResponse.getTransConfirmSuccessType() == TransConfirmSuccessType.CONFIRM_SUCCESS){
            OrderResponse orderResponse = orderManageService.confirm(orderConfirmRequest);
            Assert.isTrue(orderResponse.getSuccess(), () -> new BizException(OrderErrorCode.CREATE_ORDER_FAILED));

            return orderResponse;
        }

        return new OrderResponse.OrderResponseBuilder().buildSuccess();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Facade
    @DistributeLock(keyExpression = "#orderDiscardRequest.orderId",scene = "NORMAL_BUY_ORDER")
    public OrderResponse cancelOrder(OrderDiscardRequest orderDiscardRequest) {

        TransactionCancelResponse transactionCancelResponse = transactionLogService.cancelTransaction(new TccRequest(orderDiscardRequest.getOrderId(), "normalBuy", "ORDER"));
        Assert.isTrue(transactionCancelResponse.getSuccess(), "transaction cancel failed");

        //如果发生空回滚,或者回滚幂等,则不进行废弃订单操作
        if (transactionCancelResponse.getTransCancelSuccessType() == TransCancelSuccessType.CANCEL_AFTER_TRY_SUCCESS
                || transactionCancelResponse.getTransCancelSuccessType() == TransCancelSuccessType.CANCEL_AFTER_CONFIRM_SUCCESS) {
            OrderResponse orderResponse = orderManageService.discard(orderDiscardRequest);
            Assert.isTrue(orderResponse.getSuccess(), () -> new BizException(OrderErrorCode.UPDATE_ORDER_FAILED));
            return orderResponse;
        }

        return new OrderResponse.OrderResponseBuilder().buildSuccess();
    }
}

总结

本方案通过 TCC 模式,在非高并发的普通下单场景下,以较低的实现成本实现了跨服务事务的最终一致性。核心优势在于 “轻量级”(无需依赖 Seata 等中间件)、“高可靠”(解决 TCC 典型问题 + 异常补偿)、“可复用”(框架与业务解耦)。不过这套方案的不少逻辑都还需完善,最终没有上到生产系统,生产采用的是秒杀场景的那套,不过对于学习TCC来说本质是一样的。