Skip to content

微服务数据一致性:从分布式事务到最终一致性的实践之路

引子:一次"数据不一致"引发的事故

2024 年某电商平台大促期间,技术团队发现了一个诡异的问题:

用户下单后,订单系统显示"支付成功",但库存系统却没有扣减库存。更糟糕的是,同一笔订单被重复扣款两次,而仓库只发了一件货。

问题定位后发现,根源在于微服务拆分后,订单服务、支付服务、库存服务之间的数据一致性没有得到妥善保障。

这正是微服务架构下最经典的挑战:如何在分布式环境中保证数据一致性?


一、为什么微服务会有数据一致性问题?

1.1 单体架构 vs 微服务架构

在单体应用中,所有数据都在同一个数据库中,事务的 ACID 特性由数据库天然保证:

用户下单 → 扣减库存 → 创建订单 → 扣款
    ↓         ↓          ↓        ↓
  同一个数据库事务,要么全成功,要么全回滚

但微服务拆分后,每个服务有自己的数据库:

订单服务      库存服务      支付服务
   ↓            ↓            ↓
DB_Order    DB_Inventory   DB_Payment
   ↓            ↓            ↓
  跨服务调用,无法保证原子性

1.2 CAP 定理的约束

CAP 定理告诉我们,分布式系统无法同时满足:

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance(分区容错性)

微服务架构下,网络分区是必然的(P 必须满足),所以我们只能在 CP 和 AP 之间做选择。

关键洞察:大多数互联网业务场景下,可用性 > 强一致性


二、数据一致性方案全景图

2.1 强一致性方案

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

原理

  • 阶段一:协调者询问所有参与者"能否提交?"
  • 阶段二:如果都同意,协调者通知"提交";否则通知"回滚"

优点

  • 强一致性保证
  • 实现相对简单

缺点

  • ⚠️ 同步阻塞:所有参与者在等待期间无法做任何事
  • ⚠️ 单点故障:协调者挂了,整个事务卡死
  • ⚠️ 性能差:多次网络往返,吞吐量低

适用场景:金融核心系统、对一致性要求极高的场景


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

原理

  • Try:预留资源(冻结库存、预扣款)
  • Confirm:确认提交(真正扣减)
  • Cancel:取消预留(释放资源)

代码示例

java
// 库存服务的 TCC 实现
public class InventoryTCC {
    
    @TccTry
    public void tryReserve(Long orderId, Integer quantity) {
        // 冻结库存,不真正扣减
        inventoryMapper.freeze(orderId, quantity);
    }
    
    @TccConfirm
    public void confirm(Long orderId) {
        // 真正扣减冻结的库存
        inventoryMapper.deduct(orderId);
    }
    
    @TccCancel
    public void cancel(Long orderId) {
        // 释放冻结的库存
        inventoryMapper.unfreeze(orderId);
    }
}

优点

  • 无同步阻塞,性能优于 2PC
  • 各服务独立控制资源

缺点

  • ⚠️ 业务侵入性强:每个服务都要实现三个接口
  • ⚠️ 空回滚/悬挂问题:需要额外处理边界情况
  • ⚠️ 幂等性要求:Confirm/Cancel 可能被重复调用

2.2 最终一致性方案

方案三:本地消息表

核心思想:把分布式事务拆成本地事务 + 异步消息

流程

  1. 订单服务创建订单,同时在本地消息表插入一条消息
  2. 定时任务扫描消息表,发送消息到 MQ
  3. 库存服务消费消息,扣减库存
  4. 库存服务回执确认,订单服务标记消息完成

关键设计

sql
-- 本地消息表
CREATE TABLE outbox_message (
    id BIGINT PRIMARY KEY,
    business_id BIGINT,      -- 业务 ID(订单 ID)
    message_type VARCHAR(50), -- 消息类型
    payload JSON,            -- 消息内容
    status VARCHAR(20),      -- 状态:PENDING/SENT/COMPLETED
    retry_count INT,         -- 重试次数
    created_at TIMESTAMP
);

优点

  • ✅ 保证消息一定发送(本地事务保证)
  • ✅ 业务侵入性低
  • ✅ 支持重试和幂等

缺点

  • ⚠️ 需要轮询消息表,有延迟
  • ⚠️ 消息表数据量大,需要定期清理

方案四:Saga 模式

核心思想:把长事务拆成多个本地短事务,每个事务有对应的补偿操作

流程

订单创建 → 库存扣减 → 支付扣款 → 发货
   ↓          ↓          ↓       ↓
补偿:取消   补偿:恢复  补偿:退款  补偿:召回

实现方式

  • 编排式:有一个中心协调器控制流程
  • 协同式:各服务自己决定下一步

优点

  • ✅ 无锁、无阻塞,性能高
  • ✅ 适合长流程业务
  • ✅ 各服务松耦合

缺点

  • ⚠️ 补偿逻辑复杂,需要考虑部分成功的情况
  • ⚠️ 隔离性问题(中间状态可能被看到)

三、生产环境选型策略

3.1 决策树

是否需要强一致性?
├─ 是 → 金融核心业务?
│   ├─ 是 → 2PC 或 TCC
│   └─ 否 → 本地消息表 + 人工对账

└─ 否 → 业务流程长度?
    ├─ 短流程(2-3 步)→ 本地消息表
    └─ 长流程(>3 步)→ Saga 模式

3.2 电商场景实战

订单创建流程(最终一致性):

用户下单

订单服务:创建订单(状态:待支付)

发送消息 → MQ

库存服务:预扣库存
支付服务:发起支付

用户支付成功

支付服务:扣款成功 → 发送消息

订单服务:更新状态(已支付)
库存服务:正式扣减

关键点

  • 支付前只预扣,不真正扣减
  • 支付成功后才正式扣库存
  • 超时未支付,自动取消订单,释放库存

四、踩坑实录与解决方案

坑 1:消息重复消费

问题:MQ 消息可能被重复投递,导致库存重复扣减

解决

java
// 幂等性检查
public void deductStock(Long orderId, Integer quantity) {
    // 先检查是否已处理
    if (processedOrders.contains(orderId)) {
        log.warn("订单已处理,跳过:{}", orderId);
        return;
    }
    
    // 使用数据库唯一约束
    try {
        stockMapper.deduct(orderId, quantity);
        processedOrders.add(orderId);
    } catch (DuplicateKeyException e) {
        log.warn("重复扣减,已存在:{}", orderId);
    }
}

坑 2:补偿操作失败

问题:Saga 回滚时,补偿操作本身也可能失败

解决

  • 补偿操作也要支持重试
  • 设置最大重试次数,超过后转人工处理
  • 记录完整的操作日志,便于追溯

坑 3:数据不一致的"中间状态"被用户看到

问题:用户下单后,订单显示成功,但库存还没扣

解决

  • 前端展示上做"乐观处理"(先显示成功,后台异步确认)
  • 关键状态变更通过 WebSocket 推送更新
  • 设置合理的超时时间,超时后主动查询确认

五、最佳实践总结

5.1 设计原则

  1. 能不用分布式事务就不用:优先通过业务设计规避
  2. 能最终一致就不要强一致:大多数场景不需要强一致
  3. 补偿比回滚更重要:分布式系统要习惯"先成功,后补偿"
  4. 幂等是基本功:所有操作都要考虑重复执行的情况

5.2 技术选型

场景推荐方案理由
金融转账2PC/TCC强一致性要求
电商下单本地消息表性能与一致性平衡
跨境物流Saga长流程、多参与方
数据同步CDC+MQ最终一致、低延迟

5.3 监控与告警

  • 监控事务完成率、补偿触发率
  • 设置不一致数据量的阈值告警
  • 定期跑对账任务,发现并修复不一致

结语

微服务数据一致性没有银弹,只有权衡。

核心思想

  • 理解业务真正需要的一致性级别
  • 选择与业务匹配的技术方案
  • 接受"不完美",用补偿和对账兜底

记住:好的架构不是没有问题的架构,而是问题可控、可恢复的架构。


参考资料

  • 《分布式事务:从原理到实践》
  • Alibaba Seata 官方文档
  • 《微服务架构设计模式》