背景

面试一些候选人时候大多会说自己用过分布式事务,而且大多是使用Seta这个框架。但问题避免全局锁的时候大多会说自己使用了TCC这种模式。

但实际上大多候选人并没有真正实现过TCC分布式事务,问到一些细节时候就答不上来。

所以这里就简单实现一个基于Spring Cloud + Feign的TCC分布式事务,以便面试的后续人有资料可以搜索到。

架构

假设有两个微服务:

OrderService:负责创建订单
InventoryService:负责扣减库存(Feign RPC 被调用方)

整体流程如下:

OrderService (TCC协调者)
    |
    ├── Try 阶段: 调用 InventoryService.tryReserve()
    ├── Confirm 阶段: 调用 InventoryService.confirmReserve()
    └── Cancel 阶段: 调用 InventoryService.cancelReserve()

FeignClient 定义

@FeignClient(name = "inventory-service", path = "/inventory")
public interface InventoryServiceFeign {

    @PostMapping("/tryReserve")
    R<Boolean> tryReserve(@RequestBody InventoryTryDTO dto);

    @PostMapping("/confirmReserve")
    R<Boolean> confirmReserve(@RequestBody InventoryConfirmDTO dto);

    @PostMapping("/cancelReserve")
    R<Boolean> cancelReserve(@RequestBody InventoryCancelDTO dto);
}

服务端实现

@RestController
@RequestMapping("/inventory")
public class InventoryController {

    @Autowired
    private InventoryService inventoryService;

    @PostMapping("/tryReserve")
    public R<Boolean> tryReserve(@RequestBody InventoryTryDTO dto) {
        return R.ok(inventoryService.tryReserve(dto));
    }

    @PostMapping("/confirmReserve")
    public R<Boolean> confirmReserve(@RequestBody InventoryConfirmDTO dto) {
        return R.ok(inventoryService.confirmReserve(dto));
    }

    @PostMapping("/cancelReserve")
    public R<Boolean> cancelReserve(@RequestBody InventoryCancelDTO dto) {
        return R.ok(inventoryService.cancelReserve(dto));
    }
}

Service层伪代码

@Service
public class InventoryService {

    // Try:冻结库存
    public boolean tryReserve(InventoryTryDTO dto) {
        // 检查库存是否足够
        // 若足够 -> 记录冻结记录 (state=TRY)
        // 库存临时锁定 / 扣减冻结量
        return true;
    }

    // Confirm:正式扣减
    public boolean confirmReserve(InventoryConfirmDTO dto) {
        // 查找冻结记录 (state=TRY)
        // 扣减实际库存,删除冻结记录
        return true;
    }

    // Cancel:取消冻结
    public boolean cancelReserve(InventoryCancelDTO dto) {
        // 查找冻结记录 (state=TRY)
        // 释放冻结库存
        return true;
    }
}

OrderService 实现(TCC 协调者)

@Service
public class OrderService {

    @Autowired
    private InventoryServiceFeign inventoryFeign;

    @Transactional
    public void createOrder(OrderDTO orderDTO) {
        try {
            // Step 1: Try 阶段
            inventoryFeign.tryReserve(new InventoryTryDTO(orderDTO.getProductId(), orderDTO.getCount()));

            // Step 2: 创建订单 (本地事务)
            // saveOrder(orderDTO);

            // Step 3: Confirm 阶段(通常由事务消息触发或协调器统一调度)
            inventoryFeign.confirmReserve(new InventoryConfirmDTO(orderDTO.getOrderId()));

        } catch (Exception e) {
            // Step 4: Cancel 阶段
            inventoryFeign.cancelReserve(new InventoryCancelDTO(orderDTO.getOrderId()));
            throw e;
        }
    }
}

冗余库存总数处理

如果有一张表冗余了产品的库存总数,则需要额外增加一个库存冻结字段。

冗余库存总数处理