分布式事务解决方案:我的一次实战经验分享
引言:为什么分布式事务是个“老大难”?

在系统架构从单体逐渐走向微服务的过程中,我深刻体会到了一个原本在单数据库下轻松处理的问题——事务一致性,开始变得复杂而棘手。尤其是在多个服务之间需要对多个资源进行协调操作时,“本地事务”已经无法满足需求,于是不得不面对“分布式事务”的挑战。
这篇文章,是我亲身经历的一个真实项目中,如何解决跨服务、跨数据库的事务一致性问题的过程记录。通过这次实践,我不仅深入理解了不同场景下的分布式事务方案,也踩了不少坑,积累了宝贵的经验。希望这些内容,能对你有所帮助。
项目背景:订单中心拆分引发的一系列问题

我们公司是一家电商平台,早期所有的业务逻辑都在一个单体应用里,其中订单模块是最核心的部分。后来随着业务增长,我们决定将订单、库存、支付这三个模块独立拆分为不同的微服务,以提升系统的可维护性和扩展性。
拆分后的一个重要场景是:
用户下单时,订单服务需要创建订单、调用库存服务扣减库存、并通过支付服务完成支付状态更新。这三个服务分别连接不同的数据库。
问题来了:这三个操作要么全部成功,要么全部失败,不能出现订单已创建但库存没扣减,或者库存被扣减但订单未生成的情况。这时,传统的本地事务已经不适用,必须引入“分布式事务”机制来保障一致性。
遇到的挑战:不是所有问题都适合XA或TCC

一开始我们考虑过使用两阶段提交(2PC)这种经典的分布式事务协议,但实际测试下来发现性能差得惊人,而且一旦某个服务挂掉就会导致整个流程卡死,严重影响用户体验。
我们也调研过一些TCC(Try-Confirm-Cancel)框架,比如 Seata,理论上支持跨服务的事务管理。但实际接入的时候却发现学习成本高,尤其对于我们这种异构的服务结构(部分基于 Spring Boot,部分基于 Go),兼容性并不理想。
最终我们决定采用一种折中的方式:结合本地事务 + 最终一致性的补偿机制(Saga 模式) + 异步队列的方式,来实现一个相对可控且低耦合的分布式事务流程。
我们的解决方案:本地事务 + Saga + MQ 补偿机制
1. 主要思路
我们将下单流程拆解为以下几个关键步骤:
- 订单服务创建订单并写入数据库;
- 库存服务接收到消息,尝试扣减库存;
- 支付服务接收到消息,完成支付扣款;
- 如果某一步失败,则触发反向操作,比如回滚库存、取消订单等。
这里的关键点在于:
- 使用 RabbitMQ / Kafka 作为异步通信工具;
- 每个步骤完成后发送事件消息,后续服务监听并执行操作;
- 若发生异常,则通过重试和补偿机制保证最终一致性。
2. 系统设计图概览
[用户] → [订单服务]
↓
[本地事务写入订单表]
↓
发送「订单创建」事件至 MQ
↓
[库存服务消费事件] → 扣库存 → 失败则发送补偿消息
↓
[支付服务消费事件] → 更新支付状态 → 失败也补偿
这样,虽然不保证强一致性,但我们确保了最终一致性,并且大大降低了服务之间的耦合度和事务阻塞时间。
核心代码片段与实现细节
以下是我们订单创建主流程的一些核心逻辑示例(使用 Java + Spring Boot + RabbitMQ):
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
@Transactional
public String createOrder(OrderDTO orderDTO) {
// 1. 写入本地数据库
OrderEntity order = new OrderEntity();
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setStatus("CREATED");
order = orderRepository.save(order);
// 2. 向MQ发送事件
try {
rabbitTemplate.convertAndSend("order_exchange", "order.created", order);
} catch (Exception e) {
// 这里可以做降级处理,比如记录日志+人工介入
throw new RuntimeException("消息发送失败");
}
return order.getId();
}
}
接下来,是库存服务监听订单创建事件并执行扣减库存的逻辑:
@Component
public class InventoryConsumer {
@Autowired
private InventoryService inventoryService;
@RabbitListener(queues = "inventory.queue")
public void onOrderCreated(String message) {
ObjectMapper mapper = new ObjectMapper();
OrderEntity order = null;
try {
order = mapper.readValue(message, OrderEntity.class);
} catch (Exception e) {
log.error("解析订单消息失败", e);
return;
}
boolean success = inventoryService.reduceStock(order.getProductId(), 1);
if (!success) {
// 扣减失败,发送补偿事件或标记需手动处理
log.warn("库存不足,需补偿或提醒人工处理");
sendCompensationMessage(order);
}
}

private void sendCompensationMessage(OrderEntity order) {
// 发送补偿事件,例如通知订单服务作废该订单
}
}
可以看到,我们在每一步都进行了错误处理,并保留了手动干预的可能性,从而在异常情况下也能保障数据的最终一致性。
踩过的几个坑:教训比成功更值钱
在整个开发过程中,有几个坑让我印象特别深刻:
坑一:MQ消息丢失问题
最初我们没有开启持久化,也没有设置 confirm 和 ack 机制,结果压测时出现了不少消息丢失的问题。
解决方法:
- 对 MQ 的 exchange、queue、message 都设置
durable持久化; - 生产端开启 confirm 模式,确认消息是否投递成功;
- 消费端关闭自动 ack,改为手动确认;
- 消费失败的消息进入死信队列,后续可以重新消费或报警处理。
坑二:幂等性设计缺失
因为网络波动等原因,同一个事件可能被多次消费,导致库存被多扣。
解决方法:
- 在每个业务操作前加唯一标识,如订单ID + 操作类型;
- 使用 Redis 或数据库记录已处理的事件,避免重复执行。
if (redis.exists("processed_event:" + orderId)) {
return;
}
坑三:补偿流程过于复杂导致难以维护
最开始我们希望通过自动化完全解决所有失败场景,结果发现各种状态组合太多,代码越来越复杂。
解决方法:
- 明确哪些错误必须自动化补偿,哪些交给人工处理;
- 设置清晰的补偿边界,不要无限延伸;
- 增加日志追踪和监控,便于定位问题。
实施效果:系统稳定性显著提升
上线后,我们的分布式事务流程表现稳定,主要体现在:
- 订单创建成功率提高到了 99.8% 以上;
- 数据最终一致性基本能在几秒内达成;
- 系统性能相比传统2PC有明显优势,QPS 提升约 40%;
- 日志和告警机制帮助我们及时发现问题并快速响应。
我的经验总结:给你的建议

如果你也在面对微服务下的分布式事务问题,不妨参考以下几个建议:
✅ 1. 不要盲目追求“强一致性”
很多时候“最终一致性”才是更合理的权衡点,尤其是涉及多个团队协作的系统中。
✅ 2. 技术选型要根据实际业务情况来定
像 TCC、SAGA、MQ 等方案都有各自的优劣。比如 SAGA 更适合长周期任务,而 TCC 更适合高并发短事务的场景。
✅ 3. 幂等性、去重、补偿机制要提前规划好
别等到出问题再补救。很多线上事故其实都能通过前期的设计规避。
✅ 4. 监控和日志是你的好朋友
一定要建立完善的监控体系,特别是跨服务调用链的追踪能力(推荐使用 SkyWalking 或 Zipkin)。
✅ 5. 保持简单,拒绝过度设计
有时候,一个定时任务扫表补偿,比一堆复杂的代码更容易维护。
结语:技术始终服务于业务
回顾这次分布式事务的实践,它不仅仅是一次技术方案的选择,更是一次架构思维的转变。
我们从原来的“一切由事务控制”过渡到了“拥抱最终一致性”,从“同步调用”转向了“异步驱动”。这不仅是技术上的进步,更是工程思维的成长。
或许在未来某一天,我们会遇到更适合的框架或中间件,能让我们更优雅地解决这个问题。但在那之前,我们要做的,就是立足当前环境,做出最合理的选择。
愿你在微服务的路上少走弯路,写出可靠又高效的系统。共勉!

评论 0