分布式事务为什么难:从CAP到业务拆分的必然
后端开发中微服务架构的普及让分布式事务成为日常而非例外。一个电商下单流程横跨订单、库存、支付、积分四个服务,任何一个步骤失败都需要回滚或补偿。Spring Boot框架和Java/Go实战中,本地事务的ACID保证在跨服务场景下失效,高并发设计必须接受BASE约束——基本可用、软状态、最终一致性。分布式事务的解决方案不是消灭不一致,而是用架构手段把不一致窗口压缩到业务可接受的范围,同时保障服务治理的稳定性。
三种主流方案的定位:
- 2PC/3PC:强一致性,同步阻塞,吞吐受限,适合传统数据库跨库事务
- TCC:业务层补偿,每个参与者实现Try/Confirm/Cancel三个接口
- Saga:长事务编排,正向执行+逆向补偿,最终一致性
Saga模式:编排式与协同式
Saga模式将长事务拆分为多个本地事务序列,每个本地事务完成后发送事件触发下一个步骤,失败时执行反向补偿事务。关键区别在于编排方式。
编排式Saga(Orchestration)
由中央协调器(Saga Orchestrator)统一控制事务流程,各参与者只暴露标准接口。适合复杂业务流程和消息中间件集成场景。
// Saga编排器 - 订单创建流程
@Service
public class CreateOrderSaga {
private final SagaDefinition<CreateOrderState> definition;
public CreateOrderSaga(OrderServiceClient orderClient,
InventoryServiceClient inventoryClient,
PaymentServiceClient paymentClient,
PointServiceClient pointClient) {
this.definition = SagaDefinition
.step("reserve-inventory")
.invokeParticipant(order -> inventoryClient.reserve(order.getSkus()))
.withCompensation(order -> inventoryClient.release(order.getSkus()))
.step("create-payment")
.invokeParticipant(order -> paymentClient.charge(order.getAmount()))
.withCompensation(order -> paymentClient.refund(order.getPaymentId()))
.step("deduct-points")
.invokeParticipant(order -> pointClient.deduct(order.getPoints()))
.withCompensation(order -> pointClient.addBack(order.getPoints()))
.step("confirm-order")
.invokeParticipant(order -> orderClient.confirm(order.getId()))
.withCompensation(order -> orderClient.reject(order.getId()))
.build();
}
public SagaDefinition<CreateOrderState> getDefinition() {
return definition;
}
}
协同式Saga(Choreography)
各服务通过事件驱动自主决策,无中央协调器。适合简单流程,但业务逻辑分散在各服务中,调试和追踪困难。
// 协同式Saga - Go实现
// 订单服务
func (s *OrderService) CreateOrder(ctx context.Context, cmd CreateOrderCmd) error {
order := NewOrder(cmd)
if err := s.orderRepo.Save(ctx, order); err != nil {
return err
}
// 发布订单创建事件,触发下游服务
return s.eventBus.Publish(ctx, OrderCreatedEvent{
OrderID: order.ID,
SKUs: cmd.SKUs,
Amount: cmd.Amount,
Points: cmd.Points,
})
}
// 库存服务 - 订阅订单创建事件
func (h *InventoryHandler) HandleOrderCreated(ctx context.Context, evt OrderCreatedEvent) error {
err := h.inventorySvc.Reserve(ctx, evt.SKUs)
if err != nil {
// 库存预留失败,发布补偿事件
h.eventBus.Publish(ctx, InventoryReserveFailedEvent{OrderID: evt.OrderID})
return err
}
return h.eventBus.Publish(ctx, InventoryReservedEvent{OrderID: evt.OrderID})
}
TCC模式:业务层补偿的三阶段
TCC(Try-Confirm-Cancel)要求每个参与者实现三个接口,协调器统一调用。与Saga的最大区别是Try阶段预留资源但不提交,Confirm阶段才真正生效,Cancel阶段释放预留资源。业务中台建设中,TCC适合资金类、库存类等对一致性要求高的场景。
Try/Confirm/Cancel接口设计
// TCC参与者接口定义
public interface TccParticipant<T> {
/** 预留资源,冻结但不提交 */
TryResult tryPhase(T params);
/** 确认提交,消费预留资源 */
ConfirmResult confirmPhase(String xid);
/** 取消回滚,释放预留资源 */
CancelResult cancelPhase(String xid);
}
// 库存TCC实现
@Component
public class InventoryTccParticipant implements TccParticipant<ReserveCmd> {
@Override
public TryResult tryPhase(ReserveCmd cmd) {
// 冻结库存:从可用库存扣减,写入冻结记录
int affected = inventoryMapper.freezeStock(
cmd.getSkuId(), cmd.getQuantity(), cmd.getXid()
);
if (affected == 0) {
return TryResult.fail("库存不足");
}
return TryResult.success(cmd.getXid());
}
@Override
public ConfirmResult confirmPhase(String xid) {
// 确认扣减:删除冻结记录,库存已扣减生效
int affected = inventoryMapper.confirmFrozen(xid);
return affected > 0 ? ConfirmResult.success() : ConfirmResult.fail("冻结记录不存在");
}
@Override
public CancelResult cancelPhase(String xid) {
// 释放冻结库存:恢复可用库存,删除冻结记录
int affected = inventoryMapper.releaseFrozen(xid);
return affected > 0 ? CancelResult.success() : CancelResult.fail("冻结记录不存在");
}
}
TCC协调器实现
@Service
public class TccCoordinator {
private final List<TccParticipant<?>> participants;
private final TransactionLogRepository logRepo;
public CoordinationResult execute(TccTransaction tx) {
String xid = generateXid();
List<String> confirmed = new ArrayList<>();
// Phase 1: Try
for (TccParticipant<Object> participant : tx.getParticipants()) {
TryResult result = participant.tryPhase(tx.getParams());
if (!result.isSuccess()) {
// Try失败,回滚已成功的参与者
rollback(xid, confirmed);
return CoordinationResult.rollback(result.getReason());
}
confirmed.add(participant.getName());
}
// Phase 2: Confirm
for (String name : confirmed) {
TccParticipant<?> p = findParticipant(name);
ConfirmResult result = p.confirmPhase(xid);
if (!result.isSuccess()) {
// Confirm失败需要人工介入(不应发生)
logRepo.markAsHeuristic(xid, name);
return CoordinationResult.heuristic(xid, name);
}
}
return CoordinationResult.commit();
}
private void rollback(String xid, List<String> confirmed) {
for (String name : confirmed) {
try {
findParticipant(name).cancelPhase(xid);
} catch (Exception e) {
logRepo.markAsCompensationFailed(xid, name);
}
}
}
}
Saga vs TCC:场景化选型指南
核心差异对比
| 维度 | Saga(编排式) | TCC |
|---|---|---|
| 一致性模型 | 最终一致 | 准强一致(Try预留资源) |
| 隔离性 | 无隔离(脏读可见) | Try阶段隔离 |
| 回滚方式 | 补偿事务(正向操作的反操作) | Cancel释放预留 |
| 性能 | 高(无资源锁定) | 中等(Try阶段锁定资源) |
| 实现复杂度 | 低(标准接口) | 高(三个接口+冻结逻辑) |
| 适用场景 | 跨服务编排、长流程 | 资金、库存等强一致场景 |
选型决策树
def choose_distributed_tx_strategy(requirements):
"""分布式事务方案选型决策"""
if requirements.get('strong_consistency'):
if requirements.get('allow_resource_freeze'):
return 'TCC'
else:
return '2PC (仅限同数据库)'
if requirements.get('long_running') or requirements.get('cross_service'):
if requirements.get('complex_flow'):
return 'Saga_Orchestration'
else:
return 'Saga_Choreography'
# 默认推荐
return 'Saga_Orchestration'
# 典型场景判断
scenarios = {
"电商下单(库存+支付)": choose_distributed_tx_strategy({
"strong_consistency": True,
"allow_resource_freeze": True
}), # → TCC
"旅行预订(机票+酒店+租车)": choose_distributed_tx_strategy({
"long_running": True,
"cross_service": True,
"complex_flow": True
}), # → Saga编排式
"积分兑换(扣积分+发货)": choose_distributed_tx_strategy({
"cross_service": True,
"complex_flow": False
}), # → Saga协同式
}
消息中间件保障:至少一次交付与幂等性
无论选择Saga还是TCC,消息中间件的可靠性都是分布式事务的基础设施。API接口规范要求服务间通信必须保证”至少一次交付”(at-least-once),消费端必须实现幂等性。
RocketMQ事务消息实现
@Service
public class OrderTransactionListener implements TransactionListener {
@Autowired
private OrderService orderService;
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
String orderId = msg.getKeys();
orderService.confirmOrder(orderId);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId = msg.getKeys();
Order order = orderService.getById(orderId);
if (order == null) {
return LocalTransactionState.UNKNOW; // 继续回查
}
return order.isConfirmed()
? LocalTransactionState.COMMIT_MESSAGE
: LocalTransactionState.ROLLBACK_MESSAGE;
}
}
// 发送事务消息
@Transactional
public void createOrderWithTransaction(CreateOrderCmd cmd) {
Order order = orderService.create(cmd);
Message msg = new Message(
"ORDER_TOPIC",
"TAG_CREATE",
order.getId().getBytes(),
order.getId().getBytes() // key用于幂等
);
transactionMQProducer.sendMessageInTransaction(msg, null);
}
消费端幂等性保障
@Component
@RocketMQMessageListener(
topic = "ORDER_TOPIC",
consumerGroup = "inventory-consumer-group"
)
public class OrderMessageConsumer implements RocketMQListener<Message> {
@Autowired
private IdempotentService idempotentService;
@Override
public void onMessage(Message message) {
String msgId = message.getKeys();
// 幂等检查:已处理则跳过
if (idempotentService.isProcessed(msgId)) {
return;
}
// 执行业务逻辑
try {
inventoryService.process(message);
idempotentService.markProcessed(msgId);
} catch (Exception e) {
// 业务失败,等待重试
throw new RuntimeException(e);
}
}
}
服务治理与监控:分布式事务可观测性
微服务架构下分布式事务的调试和监控依赖分布式追踪。W3C Trace Context标准配合OpenTelemetry,可以在全局视角追踪一笔事务的完整路径:
# OpenTelemetry Collector配置
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1024
exporters:
jaeger:
endpoint: jaeger-collector:14250
tls:
insecure: true
prometheus:
endpoint: "0.0.0.0:8889"
namespace: "dist_tx"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [jaeger]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
分布式事务没有完美方案,只有合适方案。Saga适合长流程、松耦合场景,TCC适合资金库存等强一致场景。实际生产中两种方案经常混用——核心资金链路用TCC,周边积分通知用Saga。选型的本质是回答一个问题:不一致窗口业务能否容忍?能容忍就选Saga,不能就选TCC。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-saga-mo-shi-yu-tcc-fang-an-dui-bi-xuan/