分布式事务的实战经验分享:从问题到解决方案
引言:为什么我们需要关心分布式事务?

我在一家中型互联网公司做后端开发,主要负责订单系统和支付系统的维护与重构。随着业务增长,我们的系统逐渐从单体架构转向微服务架构。在这一过程中,分布式事务成了一个绕不开的问题。
尤其是当我们需要在多个服务之间同时完成“下单”、“扣减库存”、“支付”这些操作时,传统的本地事务已经无法满足一致性要求。我们遇到了不少因为分布式事务处理不当而导致的数据不一致、订单异常等问题。经过几个版本迭代和不断踩坑之后,我总结出了一套比较实用的解决方案,并在实际项目中取得了不错的效果。
今天我想结合自己的真实项目经验,跟大家分享一下我们在分布式事务上的实践过程,包括遇到的问题、踩过的坑、最终选择的方案,以及一些个人心得。
问题描述:一次典型的分布式事务场景

先说一个具体的场景:
我们有一个电商业务系统,用户下单后需要完成以下步骤:
- 下单服务创建订单
- 库存服务扣除商品库存
- 支付服务冻结用户余额并生成支付记录
这三个服务分别部署在不同的微服务中,并且使用了不同的数据库。在早期的实现中,我们尝试用本地事务来保证每一步执行成功,但很快就发现了问题:
- 如果第一步执行成功,第二步失败,订单就变成了“脏数据”,库存没扣减,但用户已经下过单了。
- 如果第三步失败,用户可能被错误地扣款,或者订单状态更新失败,导致用户重复支付。
- 更糟的是,有时候服务调用超时,我们根本不知道请求是否成功,只能让用户手动处理。
这显然不符合业务上对数据一致性的要求。于是我们开始调研分布式事务的解决方案。
解决方案:我们选择了Saga模式 + 本地事务表 + 最终一致性补偿机制

在对比了多种分布式事务方案(如TCC、Seata、2PC、MQ+事务消息等)之后,我们没有选择强一致性方案(比如Seata或XA),而是采用了Saga模式作为核心思路,结合本地事务日志表和异步补偿机制来实现最终一致性。
Saga模式简介
Saga模式是一种用于处理长周期、跨服务的分布式事务的协调方式。它由一系列本地事务组成,每个事务都对应一个正向操作和一个反向补偿操作。
例如:
- 正向操作:创建订单 → 扣除库存 → 冻结余额
- 补偿操作:取消订单 → 回补库存 → 解冻余额
如果任何一个步骤失败,则触发前面已经执行的补偿动作来回滚整体事务。
优点:
- 不依赖全局锁,性能更好
- 可以异步处理
- 适合高并发场景
缺点:
- 实现复杂,需要管理补偿逻辑
- 有可能出现中间状态的数据不一致(但可通过幂等和补偿机制解决)
本地事务日志表 + 消息队列
为了追踪整个事务的状态,我们引入了一个“事务日志表”,每一步操作完成后都会写入该表,记录当前事务ID、当前服务、操作状态等信息。并通过Kafka进行事件通知,驱动后续流程。
系统架构图简化示意如下:
[下单服务]
↓ 创建订单
[库存服务]
↓ 扣减库存
[支付服务]
↓ 冻结账户金额
↓ 成功则提交事务
↓ 失败则回滚前面所有操作
这样设计的好处是:
- 每个服务只负责自己的本地事务,避免跨服务事务的开销
- 整个流程通过消息驱动,易于扩展和监控
- 即使某个服务暂时不可用,也能通过重试机制恢复
代码实践:关键代码片段与配置示例

下面我将展示部分伪代码和结构设计,帮助大家理解具体实现方式。
事务日志表设计
CREATE TABLE `transaction_log` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`transaction_id` VARCHAR(64) NOT NULL COMMENT '全局事务ID',
`service_name` VARCHAR(64) NOT NULL COMMENT '服务名称',
`operation` VARCHAR(64) NOT NULL COMMENT '操作类型(create_order, deduct_stock, freeze_balance)',
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态(success/failure/reverted)',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME ON UPDATE CURRENT_TIMESTAMP
);
事务处理逻辑(伪代码)
public String createOrderWithDistributedTx(String userId, String productId, int quantity) {
String transactionId = UUID.randomUUID().toString();
try {
// 1. 创建订单
Order order = orderService.createOrder(userId, productId, quantity);
logTransaction(transactionId, "order-service", "create_order", "success");
// 2. 调用库存服务 -> 扣减库存
boolean stockResult = inventoryService.deductStock(productId, quantity);
if (!stockResult) throw new RuntimeException("库存不足");
logTransaction(transactionId, "inventory-service", "deduct_stock", "success");
// 3. 调用支付服务 -> 冻结余额
boolean paymentResult = paymentService.freezeBalance(userId, order.getTotalPrice());
if (!paymentResult) throw new RuntimeException("余额冻结失败");
logTransaction(transactionId, "payment-service", "freeze_balance", "success");
// 提交事务,发送最终确认消息
sendKafkaMessage("order-created", order);
} catch (Exception e) {
// 触发补偿机制
handleCompensation(transactionId);
throw e;
}
return transactionId;
}
private void handleCompensation(String transactionId) {
List<TransactionLog> logs = getLogsByTxId(transactionId);
for (TransactionLog log : logs) {
if ("success".equals(log.getStatus())) {
triggerCompensation(log.getServiceName(), log.getOperation());
}
}
}
这个只是一个简化的逻辑,实际中还需要加入幂等性、重试机制、补偿服务的解耦等细节。
Kafka消息监听补偿(伪代码)
我们在支付服务中监听“订单创建失败”的消息,收到后自动触发补偿动作:
@KafkaListener(topics = "order-compensate")
public void onOrderCompensate(String message) {
Order order = parse(message);
refundBalance(order.getUserId(), order.getTotalPrice());
}
踩坑经验:那些让我熬夜改代码的瞬间
虽然 Saga 模式听起来很美好,但在实际落地过程中,我们也踩了不少坑。
坑一:补偿逻辑幂等性没处理好,导致重复退款
在支付服务中,我们一开始没有考虑消息重复消费的问题。有一次 Kafka 消费失败导致重试,结果同一个订单被多次解冻金额,造成账务混乱。
解决方法:在补偿服务中加入幂等控制,使用 Redis 缓存“已处理的 transactionId”,防止重复执行。
if (redis.exists("compensated:" + txId)) {
return;
} else {
redis.setex("compensated:" + txId, 3600, "done");
}
坑二:事务日志写入失败导致状态丢失
在初期,我们把事务日志作为最后一步写入。结果有一次网络波动,导致日志写入失败,而实际服务已经执行完毕,无法回滚。
解决方法:将事务日志的写入放在每个服务调用之前,确保日志存在后再执行操作。哪怕后面失败,也能根据日志进行补偿。
坑三:补偿服务耦合度太高,难以维护
最开始我们将补偿逻辑分散在各个服务内部,后来发现一旦需要修改流程,得动多个服务,非常麻烦。
解决方法:我们抽象出一个独立的“补偿服务”(Compensation Service),专门监听事件总线并统一处理补偿逻辑,降低耦合度。
效果总结:上线后的变化与收益

自从我们采用这套基于 Saga 模式的分布式事务方案后,整体系统稳定性提升明显:
- 数据一致性增强:通过事务日志和补偿机制,几乎杜绝了因服务调用失败导致的数据不一致问题。
- 系统吞吐量提高:相比 Seata 的 AT 模式,这种无锁的设计显著提升了系统性能。
- 运维压力下降:补偿服务自动化处理大部分异常,减少了人工干预。
- 可扩展性强:新的业务接入只需添加对应的补偿逻辑即可,不影响原有流程。
我们还做了性能压测,在 QPS 达到 8000 左右时,系统依然能保持稳定,响应时间控制在合理范围内。
经验分享:给后端同学的一些建议
1. 分布式事务不是必须强一致
很多人一开始会认为分布式事务必须“完全一致”,但其实大多数业务场景都可以接受“最终一致性”。关键是你要清楚你的业务能不能容忍短暂的不一致。
2. 优先选轻量级方案,别上来就搞 TCC 或 XA
TCC 和 XA 虽然功能强大,但实现起来太重,尤其对中小团队来说,维护成本很高。建议先评估业务需求,再选择合适方案。
3. 日志和监控是关键
无论你用哪种方案,一定要做好事务日志记录和链路追踪。否则出了问题查半天都找不到根源。
4. 幂等性要贯穿始终
无论是接口还是回调、事件,都要考虑幂等。否则补偿机制很容易出错。
5. 后期可以考虑引入 Saga 编排框架
如果你的业务越来越复杂,也可以考虑引入一些开源的 Saga 编排框架,比如 Apache DolphinScheduler 或者自研的状态机引擎。
写在最后:技术是为业务服务的
分布式事务是一个老生常谈的话题,但它的落地却并不容易。在我参与重构的过程中,最大的收获不是学到了某种技术,而是学会了如何站在业务角度思考问题。
很多时候,我们不需要追求极致的技术方案,而是要找到一个在可控范围内、能稳定运行、能支撑业务发展的平衡点。
希望这篇文章能给大家带来一些启发,也欢迎大家在评论区交流你们在分布式事务方面的经验和看法。
文章作者:一个爱折腾的后端开发者,专注于高可用、高性能的系统设计与实现。欢迎关注我的公众号【码农小张】,获取更多干货内容。

评论 0