捐催制 发表于 2025-8-5 20:21:45

分布式事务问题的7种常见解决方案

前言

分布式事务问题,无论在面试,还是工作中经常会遇到。
分布式系统下,数据一致性不再是数据库事务那么简单的。
分布式事务作为其中最复杂的挑战之一,曾让无数团队深夜加班、焦头烂额。
今天这篇文章就跟大家一起聊聊分布式事务问题的7种常见解决方案,希望对你会有所帮助。

1.为什么分布式事务如此棘手?

在单体应用时代,数据库的ACID事务保证了数据一致性。
但在微服务架构下,一个业务操作需要跨多个服务、多个数据库,传统事务模型不再适用。
想象一下电商下单场景:

[*]订单服务创建订单(订单数据库)
[*]库存服务扣减库存(库存数据库)
[*]支付服务处理支付(支付数据库)
[*]积分服务增加积分(积分数据库)
这四个操作要么全部成功,要么全部失败。
这就是分布式事务要解决的核心问题。
那么,如何解决问题呢?
2. 常见的解决方案

2.1 2PC(两阶段提交)

该方案是强一致性方案。
2PC是最经典的分布式事务协议,通过协调者(Coordinator) 统一调度参与者(Participant) 的执行。
分为两个阶段:

第一阶段:准备阶段
协调者询问所有参与者:“能否提交事务?”
参与者执行本地事务但不提交,锁定资源并回复YES/NO。
// 参与者伪代码
public boolean prepare() {
    try {
      startTransaction();
      executeSql("UPDATE account SET frozen = 100 WHERE id = 1"); // 预留资源
      return true; // 返回YES
    } catch (Exception e) {
      rollback();
      return false; // 返回NO
    }
}第二阶段:提交/回滚阶段

[*]若所有参与者返回YES,协调者发送commit命令,参与者提交事务
[*]若有任一参与者返回NO,协调者发送rollback命令,参与者回滚事务
致命缺陷:

[*]同步阻塞:所有参与者在prepare后锁定资源,直到收到commit/rollback(高并发下吞吐量骤降)
[*]单点故障:协调者宕机导致参与者永久阻塞
[*]数据不一致:网络分区时部分参与者可能提交成功
2.2 3PC(三阶段提交)

该方案也是强一致性方案。
3PC可以解决2PC阻塞问题。
3PC在2PC基础上增加预提交阶段,并引入超时机制:


[*]CanCommit阶段:协调者询问参与者状态(不锁定资源)
[*]PreCommit阶段:参与者锁定资源并执行SQL(不提交)
[*]DoCommit阶段:正式提交
改进点:

[*]参与者超时未收到命令自动提交(降低阻塞风险)
[*]预提交阶段发现异常可提前终止
但依然存在问题:

[*]网络分区时仍可能数据不一致
[*]实现复杂度显著增加
2.3 TCC(Try-Confirm-Cancel)

该方案是最终一致性方案。
它是业务层面的2PC。
TCC将业务逻辑拆分为三个阶段:

[*]Try:预留资源(如冻结库存)
[*]Confirm:确认操作(正式扣减库存)
[*]Cancel:释放资源(解冻库存)
// 积分服务TCC实现
public class PointsService {
   
    @Transactional
    public boolean tryDeductPoints(Long userId, int points) {
      // 检查用户积分是否充足
      UserPoints user = userPointsDao.selectForUpdate(userId);
      if (user.getAvailable() < points) {
            throw new InsufficientPointsException();
      }
      // 冻结积分
      userPointsDao.freeze(userId, points);
    }
   
    public boolean confirmDeductPoints(Long userId, int points) {
      // 实际扣减冻结积分
      userPointsDao.confirmDeduct(userId, points);
    }
   
    public boolean cancelDeductPoints(Long userId, int points) {
      // 释放冻结积分
      userPointsDao.unfreeze(userId, points);
    }
}执行流程:

[*]主业务调用所有服务的try方法
[*]全部try成功则调用confirm;任一try失败则调用cancel
优势:

[*]无全局锁:只在try阶段锁定局部资源
[*]高可用:协调者可集群部署
挑战:

[*]需手动实现回滚逻辑(业务侵入性强)
[*]所有服务需提供三种接口
金融核心系统首选:某银行跨境支付系统采用TCC方案,日均处理200万笔交易,跨5个服务的事务成功率99.99%
2.4 可靠消息最终一致性

该方案也是最终一致性方案。
可以使用RocketMQ的事务消息。
RocketMQ的事务消息完美解决本地操作与消息发送的一致性问题:

关键步骤:

[*]发送half消息(对消费者不可见)
[*]执行本地事务
[*]根据本地事务结果commit/rollback
[*]MQ定时回查未决事务
示例代码:
// 订单服务使用事务消息
public class OrderService {
   
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
   
    public void createOrder(Order order) {
      // 1. 发送half消息
      Message msg = MessageBuilder.withPayload(order).build();
      TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
            "order_topic", msg, null);
      
      // 2. 执行本地事务(在TransactionListener中实现)
    }
}

// 事务监听器
@RocketMQTransactionListener
class OrderTransactionListener implements RocketMQLocalTransactionListener {

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
      try {
            Order order = (Order) msg.getPayload();
            orderDao.save(order); // 本地事务
            return RocketMQLocalTransactionState.COMMIT;
      } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
      }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
      // 回查逻辑
      return checkOrderStatus(msg);
    }
}2.5 最大努力通知

该方案是弱一致性方案。
适用于对实时性要求低的场景(如短信通知):

[*]业务主流程完成后发送通知
[*]失败后按策略重试(如间隔1min、5min、10min)
[*]达到阈值后人工干预
// 最大努力通知服务
public class BestEffortNotifier {
   
    private static final int[] RETRY_INTERVALS = {1, 5, 10, 30, 60}; // 分钟
   
    public void notify(String event) {
      int retryCount = 0;
      while (retryCount < RETRY_INTERVALS.length) {
            try {
                if (sendNotification(event)) {
                  return; // 通知成功
                }
            } catch (Exception e) {
                // 记录日志
            }
            Thread.sleep(RETRY_INTERVALS * 60 * 1000);
            retryCount++;
      }
      alertManualIntervention(event); // 人工介入
    }
}实战经验:支付回调采用此方案,重试8次跨12小时,99.5%的通知在30分钟内成功
2.6 Seata AT模式

该方案是自动化的TCC。
Seata的AT(Auto Transaction)模式在不侵入业务代码的前提下实现分布式事务:
核心机制:

[*]全局锁:TC(事务协调器)管理内存级全局锁(替代数据库行锁)
[*]SQL代理:解析业务SQL自动生成回滚日志
[*]二阶段异步提交:极大提升吞吐量
/* 原始SQL */
UPDATE product SET stock = stock - 10 WHERE id = 1001;

/* Seata自动记录回滚日志 */
INSERT INTO undo_log (branch_id, xid,
before_image, after_image)
VALUES (?, ?,
'{"stock":100}',-- 更新前值
'{"stock":90}');-- 更新后值性能对比:
方案锁持有时间锁冲突检测耗时适用场景传统2PC500~2000ms5~20ms低并发强一致性Seata AT1~10ms0.01ms高并发最终一致性局限:

[*]不支持嵌套事务
[*]热点数据更新冲突率高
2.7 eBay事件队列

该方案是基于本地事务的最终一致性方案。
eBay提出的经典方案:

[*]将分布式操作拆分为本地事务+异步事件
[*]使用事件表确保事件不丢失
[*]通过补偿机制解决失败场景
-- 订单服务数据库
BEGIN TRANSACTION;
-- 1. 创建订单
INSERT INTO orders (...) VALUES (...);
-- 2. 记录事件(与订单在同一个事务)
INSERT INTO event_queue (event_type, payload, status)
VALUES ('ORDER_CREATED', '{"orderId":1001}', 'PENDING');
COMMIT;

-- 定时任务扫描事件表并发布该方案在早期eBay系统中每天处理1亿+事件,保证核心交易链路最终一致
3.方案的选型指南

根据业务场景选择合适方案:
方案一致性级别性能复杂度适用场景2PC/3PC强一致性低中银行核心系统TCC最终一致高高电商交易、积分体系RocketMQ事务消息最终一致高中订单创建、物流通知最大努力通知弱一致高低短信提醒、运营通知Seata AT最终一致高低微服务架构的常规业务eBay事件队列最终一致高中内部状态同步黄金法则:

[*]强一致性需求:选择2PC/ZooKeeper(牺牲性能)
[*]高并发场景:选择可靠消息/Seata AT(最终一致)
[*]弱一致性场景:最大努力通知(成本最低)
总结

经过十年演进,分布式事务解决方案已从强一致性向高性能最终一致性发展。
技术没有绝对的好坏,只有适合与否。
我曾见过团队为了追求理论上的强一致性,把系统搞得复杂不堪;也见过过度追求性能导致资金损失的血泪教训。
分布式事务的本质,是在业务需求与技术可行性之间找到平衡点。
致开发者:不必追求完美的分布式事务解决方案,适合业务场景的才是最好的。
在设计时多问自己:

[*]业务能容忍多长时间不一致?
[*]事务失败后如何补偿?
[*]是否有完善的监控和人工介入机制?
愿你在分布式系统的海洋中,乘风破浪,游刃有余。
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 分布式事务问题的7种常见解决方案