Java中的TCC模式:Try/Confirm/Cancel三个阶段的业务逻辑实现与状态管理
大家好,今天我们来聊聊分布式事务中一种重要的解决方案——TCC(Try-Confirm-Cancel)模式。在微服务架构下,服务间的调用变得频繁,数据一致性面临着严峻的挑战。TCC模式通过将一个分布式事务拆分为三个阶段,允许我们在业务层面实现最终一致性。
TCC模式简介
TCC模式是一种补偿型事务,它将一个完整的业务逻辑拆分成三个阶段:
- Try阶段: 尝试执行业务,完成所有业务检查(一致性),预留必须的业务资源(准隔离性)。Try阶段的目标是尽量尝试执行,但允许失败。
- Confirm阶段: 在Try阶段执行成功后,真正执行业务,不作任何业务检查。Confirm阶段必须保证执行成功,如果失败,需要重试。
- Cancel阶段: 在Try阶段执行失败或Confirm阶段未执行时,释放Try阶段预留的业务资源。Cancel阶段同样必须保证执行成功,如果失败,需要重试。
TCC与ACID的对比:
| 特性 | ACID | TCC |
|---|---|---|
| 隔离性 | 严格隔离(通常使用锁) | 准隔离(预留资源) |
| 一致性 | 强一致性 | 最终一致性 |
| 实现难度 | 数据库原生支持,相对简单 | 业务侵入性强,实现复杂 |
| 适用场景 | 对数据一致性要求极高的场景 | 允许最终一致性,资源允许预留的场景 |
TCC模式的优势与劣势
优势:
- 性能较高: 相对于XA等强一致性事务方案,TCC模式在Try阶段已经完成了大部分业务逻辑,减少了Confirm阶段的耗时,提高了性能。
- 资源锁定时间短: Try阶段预留资源,允许其他事务访问,减少了锁的持有时间,提高了并发能力。
- 灵活性高: TCC模式允许在业务层面定义事务行为,可以根据实际业务场景进行定制。
劣势:
- 业务侵入性强: 需要对每个参与事务的服务进行改造,实现Try、Confirm、Cancel三个接口。
- 实现复杂: 需要考虑各种异常情况,例如Try阶段失败、Confirm阶段失败、Cancel阶段失败等,需要进行重试、幂等性控制等处理。
- 开发成本高: 相对于使用数据库提供的事务支持,TCC模式需要编写大量的代码,增加了开发成本。
TCC模式的适用场景
TCC模式适用于以下场景:
- 跨数据库事务: 多个服务访问不同的数据库,无法使用XA等分布式事务协议。
- 资源允许预留: 业务允许在Try阶段预留资源,例如库存、金额等。
- 性能要求较高: 对事务的响应时间有较高要求。
- 允许最终一致性: 业务允许数据在一段时间内不一致,最终达到一致状态。
TCC模式的实现要点
在实现TCC模式时,需要考虑以下几个关键点:
- 幂等性: Confirm和Cancel操作必须保证幂等性,即多次执行的结果与执行一次的结果相同。可以通过唯一标识符(例如事务ID)来保证幂等性。
- 空回滚: 如果Try阶段未执行,Cancel阶段被执行,需要保证Cancel操作不会出错。
- 悬挂处理: 如果Confirm/Cancel操作先于Try操作执行,需要拒绝执行。
TCC模式的实现示例:商品购买
假设我们有一个简单的商品购买场景,涉及两个服务:
- 库存服务: 负责管理商品的库存。
- 订单服务: 负责创建订单。
1. 库存服务:
// 库存服务接口
interface InventoryService {
boolean tryDeduct(String productId, int quantity, String transactionId);
boolean confirmDeduct(String productId, int quantity, String transactionId);
boolean cancelDeduct(String productId, int quantity, String transactionId);
}
// 库存服务实现类
class InventoryServiceImpl implements InventoryService {
private ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>(); // 模拟库存数据
private ConcurrentHashMap<String, DeductRecord> deductRecords = new ConcurrentHashMap<>(); // 记录Try阶段的扣减记录
// 内部类,记录Try阶段的扣减信息
private static class DeductRecord {
String productId;
int quantity;
boolean confirmed; // 标识是否已经confirm
}
@Override
public boolean tryDeduct(String productId, int quantity, String transactionId) {
synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
int currentInventory = inventory.getOrDefault(productId, 0);
if (currentInventory < quantity) {
System.out.println("库存不足,productId: " + productId + ", quantity: " + quantity + ", currentInventory: " + currentInventory);
return false;
}
// 预留库存:不是真的扣减,而是记录下来
DeductRecord record = new DeductRecord();
record.productId = productId;
record.quantity = quantity;
record.confirmed = false;
deductRecords.put(transactionId, record);
System.out.println("Try 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true;
}
}
@Override
public boolean confirmDeduct(String productId, int quantity, String transactionId) {
synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
DeductRecord record = deductRecords.get(transactionId);
if (record == null || record.confirmed) {
System.out.println("Confirm 扣减库存:记录不存在或已确认,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true; // 幂等性:已经confirm过了,直接返回成功
}
int currentInventory = inventory.getOrDefault(productId, 0);
inventory.put(productId, currentInventory - quantity); // 真正扣减库存
record.confirmed = true;
System.out.println("Confirm 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId + ", currentInventory: " + inventory.get(productId));
return true;
}
}
@Override
public boolean cancelDeduct(String productId, int quantity, String transactionId) {
synchronized (productId.intern()) { // 针对每个productId加锁,避免并发问题
DeductRecord record = deductRecords.get(transactionId);
if (record == null) {
System.out.println("Cancel 扣减库存:记录不存在,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true; // 幂等性:已经cancel过了,或者根本没有try过,直接返回成功
}
if (record.confirmed) {
System.out.println("Cancel 扣减库存:已经confirm,不允许cancel,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return false; // 已经confirm过了,不能cancel
}
// 恢复库存:因为Try阶段没有真正扣减,所以这里也不需要增加库存,只需要删除记录即可
deductRecords.remove(transactionId);
System.out.println("Cancel 扣减库存成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true;
}
}
// 初始化库存数据,方便测试
public void initInventory(String productId, int initialInventory) {
inventory.put(productId, initialInventory);
}
}
2. 订单服务:
// 订单服务接口
interface OrderService {
boolean tryCreateOrder(String productId, int quantity, String transactionId);
boolean confirmCreateOrder(String productId, int quantity, String transactionId);
boolean cancelCreateOrder(String productId, int quantity, String transactionId);
}
// 订单服务实现类
class OrderServiceImpl implements OrderService {
private ConcurrentHashMap<String, Order> orders = new ConcurrentHashMap<>(); // 模拟订单数据
private InventoryService inventoryService; // 注入库存服务
public OrderServiceImpl(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
private static class Order {
String productId;
int quantity;
String transactionId;
boolean created;
}
@Override
public boolean tryCreateOrder(String productId, int quantity, String transactionId) {
synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题
// 1. 创建订单记录(但未真正创建)
Order order = new Order();
order.productId = productId;
order.quantity = quantity;
order.transactionId = transactionId;
order.created = false;
orders.put(transactionId, order);
// 2. 调用库存服务的Try阶段
boolean deductResult = inventoryService.tryDeduct(productId, quantity, transactionId);
if (!deductResult) {
System.out.println("创建订单:Try 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return false;
}
System.out.println("创建订单:Try 成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true;
}
}
@Override
public boolean confirmCreateOrder(String productId, int quantity, String transactionId) {
synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题
Order order = orders.get(transactionId);
if (order == null || order.created) {
System.out.println("Confirm 创建订单:订单不存在或已创建,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true; // 幂等性
}
// 1. 真正创建订单
order.created = true;
// 2. 调用库存服务的Confirm阶段
boolean confirmDeductResult = inventoryService.confirmDeduct(productId, quantity, transactionId);
if (!confirmDeductResult) {
System.out.println("Confirm 创建订单:Confirm 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
// 理论上Confirm应该重试,这里简单处理
return false;
}
System.out.println("Confirm 创建订单成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true;
}
}
@Override
public boolean cancelCreateOrder(String productId, int quantity, String transactionId) {
synchronized (transactionId.intern()) { // 针对每个transactionId加锁,避免并发问题
Order order = orders.get(transactionId);
if (order == null) {
System.out.println("Cancel 创建订单:订单不存在,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true; // 幂等性
}
// 调用库存服务的Cancel阶段
boolean cancelDeductResult = inventoryService.cancelDeduct(productId, quantity, transactionId);
if (!cancelDeductResult) {
System.out.println("Cancel 创建订单:Cancel 扣减库存失败,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
// 理论上Cancel应该重试,这里简单处理
return false;
}
// 删除订单记录
orders.remove(transactionId);
System.out.println("Cancel 创建订单成功,productId: " + productId + ", quantity: " + quantity + ", transactionId: " + transactionId);
return true;
}
}
}
3. 模拟事务管理器(简化版)
class TransactionManager {
private OrderService orderService;
public TransactionManager(OrderService orderService) {
this.orderService = orderService;
}
public boolean purchase(String productId, int quantity, String transactionId) {
boolean tryResult = orderService.tryCreateOrder(productId, quantity, transactionId);
if (tryResult) {
boolean confirmResult = orderService.confirmCreateOrder(productId, quantity, transactionId);
if (!confirmResult) {
System.out.println("Confirm 失败,开始回滚");
orderService.cancelCreateOrder(productId, quantity, transactionId);
return false;
}
return true;
} else {
System.out.println("Try 失败,无需回滚");
return false;
}
}
}
4. 测试代码
public class Main {
public static void main(String[] args) {
InventoryServiceImpl inventoryService = new InventoryServiceImpl();
OrderServiceImpl orderService = new OrderServiceImpl(inventoryService);
TransactionManager transactionManager = new TransactionManager(orderService);
// 初始化库存
inventoryService.initInventory("product001", 10);
// 模拟购买商品
String transactionId = UUID.randomUUID().toString();
boolean purchaseResult = transactionManager.purchase("product001", 5, transactionId);
if (purchaseResult) {
System.out.println("购买成功,transactionId: " + transactionId);
} else {
System.out.println("购买失败,transactionId: " + transactionId);
}
// 模拟购买商品,库存不足
transactionId = UUID.randomUUID().toString();
purchaseResult = transactionManager.purchase("product001", 15, transactionId);
if (purchaseResult) {
System.out.println("购买成功,transactionId: " + transactionId);
} else {
System.out.println("购买失败,transactionId: " + transactionId);
}
}
}
代码解释:
InventoryService和OrderService分别定义了库存服务和订单服务的接口,包含tryDeduct、confirmDeduct、cancelDeduct和tryCreateOrder、confirmCreateOrder、cancelCreateOrder三个方法,分别对应 TCC 模式的三个阶段。InventoryServiceImpl和OrderServiceImpl分别实现了库存服务和订单服务的接口。TransactionManager模拟了一个简单的事务管理器,负责协调库存服务和订单服务,完成商品购买的事务。- 在
tryDeduct中,我们只是预留了库存,并没有真正扣减库存。 - 在
confirmDeduct中,我们真正扣减了库存。 - 在
cancelDeduct中,我们释放了预留的库存。 - 为了保证幂等性,我们在
confirmDeduct和cancelDeduct中都进行了判断,如果已经执行过,则直接返回成功。 - 在
OrderServiceImpl中,我们先调用库存服务的tryDeduct方法,如果成功,则创建订单,并调用库存服务的confirmDeduct方法。如果tryDeduct失败,则直接返回失败。如果confirmDeduct失败,则调用库存服务的cancelDeduct方法,进行回滚。
关键点:
- 锁: 为了避免并发问题,在
InventoryServiceImpl和OrderServiceImpl的方法中使用了synchronized关键字进行加锁。 实际应用中,可以使用更细粒度的锁,例如行锁。 - 幂等性: 使用了
transactionId作为唯一标识符,判断操作是否已经执行过。 - 状态记录:
deductRecords记录了Try阶段的扣减信息,用于Confirm和Cancel阶段。 - 异常处理: 代码中只进行了简单的异常处理,实际应用中需要更完善的异常处理机制,例如重试、告警等。
TCC模式的进阶应用
除了上述简单的示例外,TCC模式还可以应用到更复杂的场景中,例如:
- 分库分表: 在分库分表的场景下,需要将TCC事务拆分成多个子事务,分别在不同的数据库中执行。
- 消息队列: 可以使用消息队列来异步执行Confirm和Cancel操作,提高系统的吞吐量。
- 状态机: 可以使用状态机来管理TCC事务的状态,简化代码的编写。
总结:业务层面的最终一致性策略
TCC模式是一种在业务层面实现最终一致性的分布式事务解决方案。它通过将一个事务拆分为Try、Confirm、Cancel三个阶段,允许在业务层面进行资源预留和补偿操作。虽然TCC模式实现较为复杂,但它在性能、灵活性和资源锁定时间等方面具有优势,适用于对数据一致性要求不是特别严格,但对性能要求较高的场景。 在实际应用中,需要根据具体的业务场景选择合适的分布式事务解决方案。