一、分布式事务的挑战
在微服务架构下,一个业务操作可能涉及多个服务的数据修改。传统的本地事务无法保证跨服务的数据一致性。
经典场景:
用户下单 → 订单服务扣库存 → 支付服务扣余额 → 物流服务创建运单
任何一步失败,都需要回滚之前的操作
二、CAP定理回顾
- C(一致性):所有节点数据一致
- A(可用性):每个请求都能得到响应
- P(分区容错):网络分区时系统仍能工作
三者只能满足其二,分布式系统必须选择CP或AP。
三、主流解决方案
方案1:2PC(两阶段提交)
阶段1:Prepare(准备)
协调者 → 所有参与者:准备提交?
参与者 → 协调者:准备就绪 ✓/✗
阶段2:Commit/Rollback(提交/回滚)
全部就绪 → Commit
任一失败 → Rollback
优点 :强一致性
缺点:同步阻塞,性能差,单点故障
方案2:TCC(Try-Confirm-Cancel)
java
// Try:预留资源
public boolean tryDeduct(String userId, BigDecimal amount) {
Account account = accountMapper.selectById(userId);
if (account.getFrozen().add(amount).compareTo(account.getBalance()) > 0) {
return false;
}
accountMapper.freeze(userId, amount);
return true;
}
// Confirm:确认提交
public void confirmDeduct(String userId, BigDecimal amount) {
accountMapper.deduct(userId, amount);
}
// Cancel:取消回滚
public void cancelDeduct(String userId, BigDecimal amount) {
accountMapper.unfreeze(userId, amount);
}
优点 :性能好,无锁
缺点:业务侵入性强,需实现三个接口
方案3:Saga模式
T1 → T2 → T3 → T4
↓ ↓ ↓ ↓
C1 ← C2 ← C3 ← C4 (补偿操作)
正向操作链 + 补偿操作链
java
// Saga编排器
public class OrderSaga {
public void execute(Order order) {
try {
orderService.create(order);
inventoryService.deduct(order);
paymentService.pay(order);
shippingService.create(order);
} catch (Exception e) {
// 按相反顺序补偿
shippingService.cancel(order);
paymentService.refund(order);
inventoryService.restore(order);
orderService.cancel(order);
}
}
}
方案4:可靠消息最终一致性
java
// 本地消息表
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
// 同一事务中写入本地消息表
localMessageMapper.insert(new LocalMessage(
order.getId(), "ORDER_CREATED", order.toString()
));
}
// 定时任务扫描消息表,投递到MQ
@Scheduled(fixedRate = 5000)
public void scanAndSend() {
List messages = localMessageMapper
.selectUnsentList();
for (LocalMessage msg : messages) {
try {
mqTemplate.send(msg.getTopic(), msg.getContent());
msg.setStatus("SENT");
localMessageMapper.updateById(msg);
} catch (Exception e) {
log.error("消息投递失败", e);
}
}
}
四、Seata框架实战
yaml
# Seata Server配置
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
java
// 使用@GlobalTransactional注解
@Service
public class OrderService {
@Autowired
private InventoryClient inventoryClient;
@Autowired
private PaymentClient paymentClient;
@GlobalTransactional(name = "create-order", timeoutMills = 30000)
public Order createOrder(OrderRequest request) {
// 1. 创建订单
Order order = orderMapper.insert(request);
// 2. 扣减库存(远程调用)
inventoryClient.deduct(request.getProductId(), request.getQuantity());
// 3. 创建支付单(远程调用)
paymentClient.create(order.getId(), request.getAmount());
return order;
}
}
五、方案对比与选型
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 传统数据库 |
| TCC | 最终一致 | 高 | 高 | 资金类业务 |
| Saga | 最终一致 | 高 | 中 | 长流程业务 |
| 可靠消息 | 最终一致 | 高 | 低 | 异步场景 |
选型建议
- 强一致性要求:TCC
- 最终一致性可接受:Saga 或 可靠消息
- 简单场景:本地消息表
- 对性能要求高:Saga
六、常见问题
Q1:空回滚怎么办?
A:记录事务状态,执行Cancel前检查是否已执行Try
Q2:悬挂问题?
A:通过全局事务ID关联,保证幂等性
Q3:补偿操作也失败了?
A:引入重试机制 + 人工干预兜底
七、总结
分布式事务没有银弹,需要根据业务特点选择合适方案:
- 核心原则:BASE理论(基本可用、软状态、最终一致)
- 设计思路:宁可补偿,不可阻塞
- 工程实践:幂等性是基础,重试是保障
思考题:你的项目中如何处理分布式事务?有没有遇到过数据不一致的问题?
个人观点,仅供参考