Backend Development 8 min read

Understanding the TCC (Try‑Confirm‑Cancel) Pattern for Distributed Transactions in Microservices

This article explains the TCC (Try‑Confirm‑Cancel) distributed transaction pattern, compares it with traditional solutions, details its three‑phase workflow, provides Java code examples for each phase, and discusses exception handling, timeout control, asynchronous processing, suitable scenarios, and common pitfalls for backend microservice development.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Understanding the TCC (Try‑Confirm‑Cancel) Pattern for Distributed Transactions in Microservices

Introduction : In modern e‑commerce systems, a single order often requires creating the order, deducting inventory, freezing coupons, and reducing account balance across multiple microservices. Ensuring these operations either all succeed or all roll back is the core challenge of distributed transactions, which the TCC pattern addresses.

Common Distributed Transaction Solutions

Traditional approaches include 2PC, local message tables, and Saga, each with limitations such as blocking performance, complexity, or heavy business intrusion. TCC stands out by reserving business resources in a Try phase and confirming or canceling them later, making it suitable for high‑concurrency scenarios despite requiring business refactoring.

TCC Advantages

Handles long‑running transactions across multiple business systems.

Supports rapid resource release.

Provides strong consistency for financial transactions.

Core Principle of TCC

The pattern consists of three stages:

Try : Reserve business resources (e.g., freeze inventory, pre‑lock amount).

Confirm : Execute the actual business operation, permanently deducting resources.

Cancel : Release the reserved resources if the transaction cannot be completed.

Real‑World Example

When a user purchases a Switch console, the Try phase creates a pending order, freezes one unit of inventory, and freezes 2000 CNY in the user's account. The Confirm phase marks the order as paid, deducts inventory, and charges the account. The Cancel phase deletes the order and releases the frozen resources.

Code Implementation

1. Define TCC Interface

public interface OrderServiceTCC {
    @Transactional
    @RequestMapping("/try")
    boolean tryCreateOrder(OrderDTO order);

    @Transactional
    @RequestMapping("/confirm")
    boolean confirmCreateOrder(Long orderId);

    @Transactional
    @RequestMapping("/cancel")
    boolean cancelCreateOrder(Long orderId);
}

2. Try Phase Implementation

@Service
public class OrderServiceImpl implements OrderServiceTCC {
    // Pre‑create order with TRYING status
    @Override
    public boolean tryCreateOrder(OrderDTO order) {
        OrderEntity entity = new OrderEntity();
        entity.setStatus(OrderStatus.TRYING);
        entity.setAmount(order.getAmount());
        orderMapper.insert(entity);
        // Call inventory service
        inventoryService.tryLockStock(order.getSkuId(), order.getQuantity());
        // Call account service
        accountService.tryFreezeBalance(order.getUserId(), order.getAmount());
        return true;
    }
}

3. Confirm Phase Implementation

@Override
public boolean confirmCreateOrder(Long orderId) {
    OrderEntity order = orderMapper.selectById(orderId);
    // Idempotency check
    if (order.getStatus() == OrderStatus.CONFIRMED) {
        return true;
    }
    // Confirm order
    order.setStatus(OrderStatus.CONFIRMED);
    orderMapper.updateById(order);
    // Deduct inventory and amount via RPC
    inventoryService.confirmLockStock(order.getSkuId());
    accountService.confirmDeduction(order.getUserId());
    return true;
}

4. Cancel Phase Implementation

@Override
public boolean cancelCreateOrder(Long orderId) {
    OrderEntity order = orderMapper.selectById(orderId);
    // Idempotency check
    if (order.getStatus() == OrderStatus.CANCELLED) {
        return true;
    }
    // Cancel order
    order.setStatus(OrderStatus.CANCELLED);
    orderMapper.updateById(order);
    // Release resources via RPC
    inventoryService.cancelLockStock(order.getSkuId());
    accountService.cancelDeduction(order.getUserId());
    return true;
}

Exception Handling – Key Safeguards

Idempotency Control

// Example: ensure operation is idempotent
if (order.getStatus() != OrderStatus.TRYING) {
    throw new IllegalStateException("订单状态异常");
}

Empty Rollback Protection

public boolean cancelLockStock(String skuId) {
    LockRecord record = lockRecordDao.selectBySku(skuId);
    if (record == null) {
        log.warn("空回滚警告:skuId={}", skuId);
        return true;
    }
    // normal processing ...
}

Prevent Hanging Control

public boolean tryLockStock(String skuId, int quantity) {
    if (lockRecordDao.isCancelled(skuId)) {
        throw new TryAfterCancelException("禁止在Cancel后执行Try");
    }
    // normal processing ...
}

Advanced Techniques

Timeout Control Strategy

// Record expiration time in Try phase
order.setExpireTime(LocalDateTime.now().plusMinutes(15));

// Scheduled task scans expired orders
@Scheduled(cron = "0 */5 * * * ?")
public void handleTimeoutOrders() {
    List
orders = orderMapper.selectExpiredOrders();
    orders.forEach(order -> orderService.cancelCreateOrder(order.getId()));
}

Asynchronous Refactor

// Send Confirm message asynchronously via RocketMQ
public void afterTrySuccess(Long orderId) {
    rocketMQTemplate.sendAsync("ORDER_CONFIRM_TOPIC", orderId, new SendCallback() {
        @Override
        public void onSuccess(SendResult sendResult) {
            log.info("Confirm消息发送成功");
        }
        @Override
        public void onException(Throwable e) {
            // Add to retry queue
            retryQueue.add(orderId);
        }
    });
}

Suitable Scenarios & Limitations

Best suited for: high‑consistency financial transactions, inventory or coupon operations, and long‑running processes spanning multiple business systems.

Pitfalls to avoid: high business refactoring cost, increased development complexity, and misuse for simple local transactions.

Conclusion

Adopt TCC only after evaluating the necessity, design resource reservation clearly, implement robust exception handling, and integrate monitoring to track transaction status in real time.

JavaMicroservicesbackend developmentIdempotencytccdistributed transactions
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.