使用 Seata 和 TCC 模式实现分布式事务:基于 Spring Cloud Alibaba 的电商案例
Seata 简介
Seata(Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,旨在为微服务架构提供高性能和易用的分布式事务支持。Seata 支持多种事务模式,包括 AT(Automatic Transaction)、TCC(Try-Confirm-Cancel)、SAGA 和 XA,其中 TCC 模式以其灵活性和高性能在互联网场景中应用广泛。
Seata 的核心组件包括:
- TC(Transaction Coordinator):事务协调者,负责管理全局事务的状态。
- TM(Transaction Manager):事务管理器,定义全局事务的范围并发起提交或回滚。
- RM(Resource Manager):资源管理器,管理分支事务并与数据库交互。
TCC 模式简介
TCC 模式是一种基于补偿的分布式事务模式,分为三个阶段:
- Try:尝试执行业务逻辑,预留资源(如冻结库存)。
- Confirm:确认阶段,提交业务逻辑(如扣减库存)。
- Cancel:取消阶段,回滚操作(如释放冻结的库存)。
TCC 模式适用于需要高并发、高性能的场景,尤其是在互联网电商、金融等领域。它通过业务代码显式定义补偿逻辑,提供了更大的灵活性。
真实案例:电商订单服务
假设我们正在开发一个电商系统,涉及以下微服务:
- 订单服务(Order Service):创建订单并记录用户信息。
- 库存服务(Inventory Service):管理商品库存。
- 支付服务(Payment Service):处理用户支付。
业务场景
用户下单时,系统需要:
- 创建订单记录。
- 冻结商品库存。
- 扣款并记录支付状态。 如果任一步骤失败,则回滚所有操作。
数据库表设计
订单服务表
sql
CREATE TABLE `orders` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT NOT NULL,
`product_id` BIGINT NOT NULL,
`quantity` INT NOT NULL,
`total_amount` DECIMAL(10, 2) NOT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' -- PENDING, CONFIRMED, CANCELLED
);
库存服务表
sql
CREATE TABLE `inventory` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT NOT NULL,
`total_stock` INT NOT NULL,
`frozen_stock` INT NOT NULL DEFAULT 0
);
支付服务表
sql
CREATE TABLE `payment` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`order_id` BIGINT NOT NULL,
`user_id` BIGINT NOT NULL,
`amount` DECIMAL(10, 2) NOT NULL,
`status` VARCHAR(20) NOT NULL DEFAULT 'PENDING' -- PENDING, SUCCESS, FAILED
);
服务设计与调用
我们使用 Spring Cloud Alibaba、Seata TCC 和 Feign 实现服务间调用。
1. 订单服务(Order Service)
订单服务作为全局事务的发起者,协调库存和支付服务。
TCC 接口定义
java
public interface OrderTccService {
@TwoPhaseBusinessAction(name = "OrderTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
void tryCreate(OrderDTO orderDTO, @BusinessActionContextParameter(paramName = "orderId") Long orderId);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
实现
java
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private PaymentClient paymentClient;
@Override
@Transactional
public void tryCreate(OrderDTO orderDTO, Long orderId) {
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setQuantity(orderDTO.getQuantity());
order.setTotalAmount(orderDTO.getTotalAmount());
order.setStatus("PENDING");
orderMapper.insert(order);
// 调用库存服务冻结库存
inventoryClient.freezeStock(order.getProductId(), order.getQuantity());
// 调用支付服务冻结金额
paymentClient.freezePayment(order.getId(), order.getTotalAmount());
}
@Override
public boolean confirm(BusinessActionContext context) {
Long orderId = (Long) context.getActionContext("orderId");
Order order = orderMapper.selectById(orderId);
order.setStatus("CONFIRMED");
orderMapper.updateById(order);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
Long orderId = (Long) context.getActionContext("orderId");
Order order = orderMapper.selectById(orderId);
order.setStatus("CANCELLED");
orderMapper.updateById(order);
return true;
}
}
Feign 客户端
java
@FeignClient(name = "inventory-service")
public interface InventoryClient {
@PostMapping("/inventory/freeze")
void freezeStock(@RequestParam("productId") Long productId, @RequestParam("quantity") Integer quantity);
}
@FeignClient(name = "payment-service")
public interface PaymentClient {
@PostMapping("/payment/freeze")
void freezePayment(@RequestParam("orderId") Long orderId, @RequestParam("amount") BigDecimal amount);
}
2. 库存服务(Inventory Service)
库存服务实现冻结、确认和取消逻辑。
TCC 接口
java
public interface InventoryTccService {
@TwoPhaseBusinessAction(name = "InventoryTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
void freezeStock(@BusinessActionContextParameter(paramName = "productId") Long productId, Integer quantity);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
实现
java
@RestController
@RequestMapping("/inventory")
public class InventoryTccServiceImpl implements InventoryTccService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
@PostMapping("/freeze")
@Transactional
public void freezeStock(Long productId, Integer quantity) {
Inventory inventory = inventoryMapper.selectByProductId(productId);
if (inventory.getTotalStock() - inventory.getFrozenStock() < quantity) {
throw new RuntimeException("Insufficient stock");
}
inventory.setFrozenStock(inventory.getFrozenStock() + quantity);
inventoryMapper.updateById(inventory);
}
@Override
public boolean confirm(BusinessActionContext context) {
Long productId = (Long) context.getActionContext("productId");
Inventory inventory = inventoryMapper.selectByProductId(productId);
Integer quantity = (Integer) context.getActionContext("quantity");
inventory.setFrozenStock(inventory.getFrozenStock() - quantity);
inventory.setTotalStock(inventory.getTotalStock() - quantity);
inventoryMapper.updateById(inventory);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
Long productId = (Long) context.getActionContext("productId");
Inventory inventory = inventoryMapper.selectByProductId(productId);
Integer quantity = (Integer) context.getActionContext("quantity");
inventory.setFrozenStock(inventory.getFrozenStock() - quantity);
inventoryMapper.updateById(inventory);
return true;
}
}
3. 支付服务(Payment Service)
支付服务处理支付冻结和扣款。
TCC 接口
java
public interface PaymentTccService {
@TwoPhaseBusinessAction(name = "PaymentTccAction", commitMethod = "confirm", rollbackMethod = "cancel")
void freezePayment(@BusinessActionContextParameter(paramName = "orderId") Long orderId, BigDecimal amount);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
实现
java
@RestController
@RequestMapping("/payment")
public class PaymentTccServiceImpl implements PaymentTccService {
@Autowired
private PaymentMapper paymentMapper;
@Override
@PostMapping("/freeze")
@Transactional
public void freezePayment(Long orderId, BigDecimal amount) {
Payment payment = new Payment();
payment.setOrderId(orderId);
payment.setAmount(amount);
payment.setStatus("PENDING");
paymentMapper.insert(payment);
}
@Override
public boolean confirm(BusinessActionContext context) {
Long orderId = (Long) context.getActionContext("orderId");
Payment payment = paymentMapper.selectByOrderId(orderId);
payment.setStatus("SUCCESS");
paymentMapper.updateById(payment);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
Long orderId = (Long) context.getActionContext("orderId");
Payment payment = paymentMapper.selectByOrderId(orderId);
payment.setStatus("FAILED");
paymentMapper.updateById(payment);
return true;
}
}
全局事务发起
在订单服务中,使用 @GlobalTransactional
注解发起全局事务:
java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderTccService orderTccService;
@PostMapping("/create")
@GlobalTransactional
public String createOrder(@RequestBody OrderDTO orderDTO) {
orderTccService.tryCreate(orderDTO, null);
return "Order created successfully";
}
}
总结
通过 Seata 的 TCC 模式,我们实现了电商系统中的分布式事务。订单服务作为 TM 发起全局事务,库存服务和支付服务作为 RM 分别处理资源预留和补偿逻辑。使用 Spring Cloud Alibaba 的 Feign 完成服务间调用,MyBatis 实现数据库操作。这种方案在高并发场景下既保证了数据一致性,又提供了良好的性能。