【架构实战】分布式事务解决方案

一、分布式事务的挑战

在微服务架构下,一个业务操作可能涉及多个服务的数据修改。传统的本地事务无法保证跨服务的数据一致性。

经典场景:

复制代码
用户下单 → 订单服务扣库存 → 支付服务扣余额 → 物流服务创建运单

任何一步失败,都需要回滚之前的操作

二、CAP定理回顾

  • C(一致性):所有节点数据一致
  • A(可用性):每个请求都能得到响应
  • P(分区容错):网络分区时系统仍能工作

三者只能满足其二,分布式系统必须选择CP或AP。

三、主流解决方案

方案1:2PC(两阶段提交)

复制代码
阶段1:Prepare(准备)
  协调者 → 所有参与者:准备提交?
  参与者 → 协调者:准备就绪 ✓/✗

阶段2:Commit/Rollback(提交/回滚)
  全部就绪 → Commit
  任一失败 → Rollback

优点 :强一致性
缺点:同步阻塞,性能差,单点故障

方案2:TCC(Try-Confirm-Cancel)

java 复制代码
// Try:预留资源
public boolean tryDeduct(String userId, BigDecimal amount) {
    Account account = accountMapper.selectById(userId);
    if (account.getFrozen().add(amount).compareTo(account.getBalance()) > 0) {
        return false;
    }
    accountMapper.freeze(userId, amount);
    return true;
}

// Confirm:确认提交
public void confirmDeduct(String userId, BigDecimal amount) {
    accountMapper.deduct(userId, amount);
}

// Cancel:取消回滚
public void cancelDeduct(String userId, BigDecimal amount) {
    accountMapper.unfreeze(userId, amount);
}

优点 :性能好,无锁
缺点:业务侵入性强,需实现三个接口

方案3:Saga模式

复制代码
T1 → T2 → T3 → T4
↓    ↓    ↓    ↓
C1 ← C2 ← C3 ← C4  (补偿操作)

正向操作链 + 补偿操作链
java 复制代码
// Saga编排器
public class OrderSaga {
    
    public void execute(Order order) {
        try {
            orderService.create(order);
            inventoryService.deduct(order);
            paymentService.pay(order);
            shippingService.create(order);
        } catch (Exception e) {
            // 按相反顺序补偿
            shippingService.cancel(order);
            paymentService.refund(order);
            inventoryService.restore(order);
            orderService.cancel(order);
        }
    }
}

方案4:可靠消息最终一致性

java 复制代码
// 本地消息表
@Transactional
public void createOrder(Order order) {
    orderMapper.insert(order);
    
    // 同一事务中写入本地消息表
    localMessageMapper.insert(new LocalMessage(
        order.getId(), "ORDER_CREATED", order.toString()
    ));
}

// 定时任务扫描消息表,投递到MQ
@Scheduled(fixedRate = 5000)
public void scanAndSend() {
    List messages = localMessageMapper
        .selectUnsentList();
    for (LocalMessage msg : messages) {
        try {
            mqTemplate.send(msg.getTopic(), msg.getContent());
            msg.setStatus("SENT");
            localMessageMapper.updateById(msg);
        } catch (Exception e) {
            log.error("消息投递失败", e);
        }
    }
}

四、Seata框架实战

yaml 复制代码
# Seata Server配置
seata:
  enabled: true
  application-id: order-service
  tx-service-group: my_tx_group
  service:
    vgroup-mapping:
      my_tx_group: default
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
java 复制代码
// 使用@GlobalTransactional注解
@Service
public class OrderService {
    
    @Autowired
    private InventoryClient inventoryClient;
    
    @Autowired
    private PaymentClient paymentClient;
    
    @GlobalTransactional(name = "create-order", timeoutMills = 30000)
    public Order createOrder(OrderRequest request) {
        // 1. 创建订单
        Order order = orderMapper.insert(request);
        
        // 2. 扣减库存(远程调用)
        inventoryClient.deduct(request.getProductId(), request.getQuantity());
        
        // 3. 创建支付单(远程调用)
        paymentClient.create(order.getId(), request.getAmount());
        
        return order;
    }
}

五、方案对比与选型

方案 一致性 性能 复杂度 适用场景
2PC 强一致 低 中 传统数据库
TCC 最终一致 高 高 资金类业务
Saga 最终一致 高 中 长流程业务
可靠消息 最终一致 高 低 异步场景

选型建议

  • 强一致性要求:TCC
  • 最终一致性可接受:Saga 或 可靠消息
  • 简单场景:本地消息表
  • 对性能要求高:Saga

六、常见问题

Q1:空回滚怎么办?

A:记录事务状态,执行Cancel前检查是否已执行Try

Q2:悬挂问题?

A:通过全局事务ID关联,保证幂等性

Q3:补偿操作也失败了?

A:引入重试机制 + 人工干预兜底

七、总结

分布式事务没有银弹,需要根据业务特点选择合适方案:

  • 核心原则:BASE理论(基本可用、软状态、最终一致)
  • 设计思路:宁可补偿,不可阻塞
  • 工程实践:幂等性是基础,重试是保障

思考题:你的项目中如何处理分布式事务?有没有遇到过数据不一致的问题?


个人观点,仅供参考

相关推荐
isboyjc12 小时前
Lomo Agent:面向创作型、持续协作 Agent 的架构设计与工程实践
人工智能·架构·agent
聪明的笨猪猪13 小时前
AI应用架构选型:架构师的核心决策困境与权衡
经验分享·面试·架构
疯华整猫15 小时前
被杀掉之后:我把 Android 的「唤起 + 常驻」重做了一遍
前端·面试·架构
Dawson Zhu15 小时前
【LLM推理与Serving⑤】Prefix Cache前缀缓存:让重复prompt不再重复计算
人工智能·语言模型·架构·aigc·agi
薛定谔的悦15 小时前
光储充一体机从主控到执行单元的双层架构
数据库·mongodb·架构
山甫aa17 小时前
锁一篇搞定:乐观锁、悲观锁、CAS、数据库锁与分布式锁【从零开始的 Web 后端学习】
java·数据库·分布式·后端·学习·web
国科安芯1 天前
83 倍:ASC0108S 在推挽和开漏之间,差的不是速度,是回程
单片机·嵌入式硬件·架构·电平转换·抗辐射
QXWZ_IA1 天前
横跨三网“只进不出”架构如何划分安全边界
安全·架构
ZENERGY-众壹1 天前
告警风暴怎么压?100MW 电站监控平台告警引擎架构复盘
数据库·架构·iot·电站监控
严肃小白兔2 天前
Cloudflare Durable Objects 支持自动重试:分布式调用如何避免重复执行?
分布式