引言
在分布式系统中,跨服务或跨数据库的数据一致性是复杂且关键的技术挑战。订单支付、库存扣减、跨行转账等场景都需要确保多个独立操作的原子性。本文将通过核心原理剖析和Java代码示例,系统讲解分布式事务的常见解决方案,帮助开发者理解并选择适合业务场景的技术方案。
一、分布式事务的核心挑战
在分布式环境下,事务管理需应对以下问题:
- 网络不可靠性:通信延迟、消息丢失或重复可能导致数据不一致。
- 部分失败:部分节点成功,部分失败,需设计回滚或补偿机制。
- CAP定理约束:需在一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance)之间权衡。
- 性能与锁冲突:全局锁可能降低系统吞吐量。
二、分布式事务的常见解决方案
1. 两阶段提交(2PC)
原理
通过协调者(Coordinator)管理多个参与者(Participant),分两阶段提交:
- 准备阶段:协调者询问参与者是否可提交,参与者锁定资源并响应。
- 提交阶段:若所有参与者同意,协调者通知提交;否则通知回滚。
Java代码示例(Spring Boot + Atomikos)
// 配置JTA事务管理器(application.yml)
spring:
jta:
enabled: true
atomikos:
datasource:
unique-resource-name: orderDB
max-pool-size: 5
// 服务类:跨数据库操作
@Service
public class OrderService {
@Autowired
private JdbcTemplate orderJdbcTemplate; // 订单库
@Autowired
private JdbcTemplate inventoryJdbcTemplate; // 库存库
@Transactional // JTA全局事务
public void createOrder(String orderId, String productId, int count) {
orderJdbcTemplate.update("INSERT INTO orders VALUES (?, ?, ?)", orderId, productId, count);
inventoryJdbcTemplate.update("UPDATE inventory SET stock = stock - ? WHERE product_id = ?", count, productId);
}
}
关键点:
- 使用
@Transactional注解触发JTA事务。 - 优点:强一致性。
- 缺点:性能低、协调者单点故障、资源长期锁定。
2. TCC(Try-Confirm-Cancel)
原理
通过业务逻辑层实现补偿机制:
- Try阶段:预留资源(如冻结库存)。
- Confirm阶段:提交实际业务操作。
- Cancel阶段:失败时释放预留资源。
Java代码示例
// 定义TCC接口
public interface InventoryTccService {
boolean tryLock(String productId, int count);
boolean confirmLock(String productId, int count);
boolean cancelLock(String productId, int count);
}
// 实现类
@Service
public class InventoryTccServiceImpl implements InventoryTccService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public boolean tryLock(String productId, int count) {
// 冻结库存(非实际扣减)
int affected = jdbcTemplate.update(
"UPDATE inventory SET frozen = frozen + ? WHERE product_id = ? AND stock - frozen >= ?",
count, productId, count
);
return affected > 0;
}
@Override
public boolean confirmLock(String productId, int count) {
// 实际扣减库存
jdbcTemplate.update("UPDATE inventory SET stock = stock - ?, frozen = frozen - ? WHERE product_id = ?",
count, count, productId);
return true;
}
@Override
public boolean cancelLock(String productId, int count) {
// 解冻库存
jdbcTemplate.update("UPDATE inventory SET frozen = frozen - ? WHERE product_id = ?", count, productId);
return true;
}
}
使用场景:
public void createOrder(String orderId, String productId, int count) {
if (!inventoryTccService.tryLock(productId, count)) {
throw new RuntimeException("库存不足");
}
try {
orderRepository.createOrder(orderId, productId, count);
inventoryTccService.confirmLock(productId, count);
} catch (Exception e) {
inventoryTccService.cancelLock(productId, count);
}
}
优点:无全局锁,性能高;缺点:需手动实现补偿逻辑。
3. 基于消息队列的最终一致性(RocketMQ事务消息)
原理
利用消息队列的可靠投递,实现跨服务异步最终一致性:
- 生产者发送事务消息,执行本地事务。
- 消息队列定期检查事务状态,决定投递或丢弃消息。
Java代码示例
// 订单服务:发送事务消息
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Transactional
public void createOrder(String orderId, String productId, int count) {
orderRepository.createOrder(orderId, productId, count);
rocketMQTemplate.sendMessageInTransaction(
"inventory-topic",
MessageBuilder.withPayload(new InventoryDeductDTO(productId, count)).setHeader("orderId", orderId).build(),
null
);
}
}
// RocketMQ事务监听器
@RocketMQTransactionListener
public class InventoryTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
return RocketMQLocalTransactionState.UNKNOWN; // 等待检查
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String orderId = msg.getHeaders().get("orderId", String.class);
return orderRepository.existsById(orderId) ?
RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
}
}
// 库存服务:消费消息
@Service
@RocketMQMessageListener(topic = "inventory-topic", consumerGroup = "inventory-group")
public class InventoryConsumer implements RocketMQListener<InventoryDeductDTO> {
@Override
public void onMessage(InventoryDeductDTO dto) {
inventoryService.deductStock(dto.getProductId(), dto.getCount());
}
}
优点:高吞吐量,系统解耦;缺点:数据短暂不一致。
4. Saga模式(Seata框架实现)
原理
将长事务拆分为多个本地事务,失败时触发补偿操作。
- 正向操作:依次执行子事务(如创建订单、扣减库存)。
- 补偿操作:按反向顺序回滚(如删除订单、恢复库存)。
代码示例(Seata Saga)
// 定义Saga流程(JSON配置)
{
"name": "createOrderSaga",
"steps": [
{
"name": "createOrder",
"compensate": "cancelOrder",
"service": "orderService",
"input": ["orderId", "productId", "count"]
},
{
"name": "deductInventory",
"compensate": "restoreInventory",
"service": "inventoryService",
"input": ["productId", "count"]
}
]
}
// 订单服务
@Service
public class OrderService {
@SagaStart
public void createOrder(String orderId, String productId, int count) {
orderRepository.save(new Order(orderId, productId, count));
}
public void cancelOrder(String orderId, String productId, int count) {
orderRepository.deleteById(orderId);
}
}
// 库存服务
@Service
public class InventoryService {
@SagaAction
public void deductInventory(String productId, int count) {
inventoryRepository.deductStock(productId, count);
}
@SagaCompensate
public void restoreInventory(String productId, int count) {
inventoryRepository.addStock(productId, count);
}
}
优点:适合长事务;缺点:需显式管理补偿逻辑。
三、选型建议
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致性 | 低 | 高 | 金融核心系统 |
| TCC | 最终一致 | 高 | 高 | 需灵活补偿的业务 |
| 消息队列 | 最终一致 | 高 | 中 | 高并发场景(如电商) |
| Saga | 最终一致 | 中 | 中 | 长事务(如物流系统) |
四、实践注意事项
- 幂等性设计:重试机制可能导致重复调用,需确保接口幂等。
- 日志与监控:记录事务状态,便于故障排查。
- 超时与重试:设置合理超时时间,避免资源长期锁定。
- 人工兜底:极端情况下需人工介入修复数据。
五、总结
分布式事务没有“银弹”,需根据业务特点选择:
- 强一致性:优先考虑2PC或TCC。
- 高并发最终一致:消息队列或Saga模式。
- 长事务:Saga模式是更优解。
通过合理选择方案,结合框架(如Seata、RocketMQ)和代码规范,可构建高可靠的分布式系统。