在微服务架构下,数据分散在不同的服务与数据库中,传统的单体数据库事务(ACID)已无法满足跨服务的数据一致性需求,这构成了分布式事务的核心挑战。
一、分布式事务的核心挑战:从ACID到CAP
在单体应用中,我们通常依赖数据库的本地事务来保证一致性,例如在 Java 和 Spring 框架中,使用 @Transactional 注解即可轻松管理事务:
@Transactional
public void placeOrder(Order order) {
orderRepository.save(order); // 操作订单表
inventoryService.deduct(order); // 操作库存表
accountService.deductBalance(order); // 操作账户表
// 同一个数据库,同一个事务
}
然而,在微服务架构中,订单、库存、账户等数据分别由独立部署的服务管理,存储在不同的物理数据库里。这时,我们面临的是一组全新的约束——CAP定理,它表明在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)无法同时满足。
CAP定理的核心约束:
- 一致性:所有节点在同一时间看到的数据是完全相同的。
- 可用性:每个非故障节点接收到的请求都必须得到响应(不保证数据最新)。
- 分区容错性:系统在网络发生分区(部分节点无法通信)时仍然能够继续工作。
对于微服务系统,网络分区是必须容忍的现实,因此我们必须在一致性(CP)和可用性(AP)之间做出权衡,无法像单体应用那样追求完美的ACID特性。这正是分布式事务解决方案需要解决的复杂性问题。
二、分布式事务解决方案全景图
面对不同的业务场景和一致性要求,业界衍生出了多种分布式事务解决方案,其核心特征对比如下:
| 方案类型 |
代表方案 |
一致性 |
性能 |
复杂度 |
适用场景 |
| 强一致性 |
2PC / 3PC |
强一致性 |
低 |
高 |
金融核心交易(如转账) |
| 最终一致性 |
Saga、TCC、消息事务 |
最终一致性 |
中高 |
中 |
电商下单、物流跟踪 |
| 无事务 |
最大努力通知 |
弱一致性 |
高 |
低 |
日志记录、状态通知 |
三、主流解决方案深度解析
1. 两阶段提交(2PC)
2PC通过一个协调者(Coordinator)来管理多个参与者(Participant),分为投票(Prepare)和提交/回滚(Commit/Rollback)两个阶段,确保所有参与者要么全部提交,要么全部回滚。
优缺点分析:
- 优点:实现了强一致性,有XA等标准规范支持。
- 缺点:同步阻塞,性能较差,且存在协调者单点故障风险。
2. Saga模式
Saga模式将一个大事务(分布式长事务)拆分为一系列可补偿的本地小事务。每个本地事务都有对应的补偿操作,通过执行正向事务链或在失败时执行反向补偿链来保证最终一致性。
实现方式对比: |
类型 |
协同式 (Choreography) |
编排式 (Orchestration) |
| 架构 |
事件驱动,服务间直接通信 |
中央协调器统一调度 |
| 复杂度 |
低(无中心节点) |
中(需实现协调器) |
| 可维护性 |
差(业务逻辑分散在各服务) |
好(逻辑集中在协调器) |
| 事务控制 |
弱 |
强 |
协同式Saga代码示例(事件驱动):
// 订单服务 - 发布事件
@Service
@Transactional
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(OrderDTO order) {
// 1. 创建待确认订单(本地事务)
Order pendingOrder = createPendingOrder(order);
orderRepository.save(pendingOrder);
// 2. 发布“订单创建”事件,触发下游服务
eventPublisher.publishEvent(new OrderCreatedEvent(pendingOrder.getId(), order.getProductId(), order.getQuantity()));
}
// 监听库存扣减成功事件,继续下一步
@EventListener
@Transactional
public void handleInventoryDeducted(InventoryDeductedEvent event) {
// 确认订单,并可能发布扣款事件...
}
}
3. TCC模式
TCC(Try-Confirm-Cancel)是一种业务侵入性较强的方案,要求每个服务提供三个阶段的操作:
- Try:尝试执行业务,完成所有一致性检查,并预留必要资源(如冻结库存、预扣余额)。
- Confirm:确认执行业务,真正使用Try阶段预留的资源。要求幂等。
- Cancel:取消执行业务,释放Try阶段预留的资源。要求幂等。
TCC模式核心优势在于其强一致性控制,但开发复杂度高,需要精心设计资源预留和空补偿、悬挂等异常问题的防护。
4. 消息事务模式
该模式利用消息队列的可靠性,实现业务与消息发送的原子性,从而保证最终一致性。常见实现有“本地消息表”和“RocketMQ事务消息”等。
本地消息表示例:
@Component
public class LocalMessageTransactionService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private TransactionLogRepository logRepository; // 本地消息表
@Transactional
public void placeOrderWithMessage(OrderDTO order) {
// 1. 业务操作:创建订单
Order newOrder = createOrder(order);
orderRepository.save(newOrder);
// 2. 在同一个数据库事务中,插入一条待发送的消息记录
TransactionLog msgLog = new TransactionLog();
msgLog.setBusinessId(newOrder.getId());
msgLog.setStatus(MessageStatus.PENDING);
logRepository.save(msgLog);
// 事务提交
} // 事务提交后,由独立定时任务扫描本地消息表,将消息发送至MQ,并更新状态。
@Scheduled(fixedRate = 5000)
@Transactional
public void sendPendingMessages() {
List<TransactionLog> pendingMsgs = logRepository.findByStatus(MessageStatus.PENDING);
for (TransactionLog msg : pendingMsgs) {
try {
// 发送到消息队列,如 [RabbitMQ 或 Kafka](https://yunpan.plus/f/23-1)
messageSender.send("ORDER_TOPIC", msg.getContent());
msg.setStatus(MessageStatus.SENT);
logRepository.save(msg);
} catch (Exception e) {
log.error("消息发送失败", e);
}
}
}
}
四、Seata框架AT模式实战
Seata 是一款开源的分布式事务解决方案,其AT(Auto Transaction)模式对业务代码侵入极低,原理是在数据源层进行代理,自动拦截SQL生成回滚日志。
Seata AT 模式配置与使用:
- 全局事务注解:在事务发起方的方法上添加
@GlobalTransactional。
- 数据源代理:Seata会自动代理
DataSource,实现分支事务的注册和回滚日志记录。
# application.yml 配置示例
seata:
enabled: true
tx-service-group: my_tx_group # 事务组名称
config:
type: nacos # 从Nacos读取配置
nacos:
server-addr: localhost:8848
registry:
type: nacos # 向Nacos注册服务
nacos:
server-addr: localhost:8848
application: seata-server
@Service
public class SeataOrderService {
@GlobalTransactional(name = "place-order", timeoutMills = 300000, rollbackFor = Exception.class)
public void placeOrderWithSeata(OrderDTO order) {
// 1. 本地插入订单(Seata代理数据源,记录undo_log)
orderMapper.insert(order);
// 2. 远程调用库存服务(通过Feign等)
inventoryFeignClient.deductStock(order.getProductId(), order.getQuantity());
// 如果此处调用失败,Seata Server会协调回滚所有分支事务
}
}
五、生产环境选型指南与设计原则
| 方案选择决策矩阵: |
场景特征 |
推荐方案 |
核心理由 |
注意事项 |
| 金融支付 |
TCC |
强一致性要求高,资金数据敏感 |
开发复杂度高,需防悬挂、空补偿 |
| 电商下单 |
Saga / 消息事务 |
业务可容忍短暂最终一致性 |
必须实现服务幂等、补偿幂等 |
| 物流跟踪 |
最大努力通知 |
一致性要求低,信息可达即可 |
需配套完善的重试与报警机制 |
| 数据同步 |
消息事务 |
强调异步处理能力与性能 |
需处理消息顺序、去重问题 |
| 传统系统改造 |
Seata AT |
代码侵入小,改造成本低 |
数据库需支持行锁,有一定限制 |
分布式事务核心设计原则:
- 业务导向:根据业务对一致性的容忍度选择方案,不强求所有场景都实现强一致性。
- 补偿与幂等:为每个正向操作设计对应的补偿操作,且所有操作(包括重试和补偿)都必须保证幂等性。
- 监控与治理:建立完善的事务状态监控、日志追踪和告警机制,对失败事务能够快速定位和干预。