BASE 理论

某电商平台在双十一高峰期,库存系统面临巨大压力。

系统设计时,团队选择了强一致性(CP)路线,所有库存扣减必须实时同步到所有节点。双十一零点期间,由于流量暴增,同步延迟从 5ms 飙升到 500ms,系统开始出现大量超时。

更糟糕的是,由于强一致性的要求,系统在高延迟情况下开始拒绝用户请求——因为无法在合理时间内完成跨节点同步。结果是:用户在购物车页面等待 30 秒后收到"系统繁忙"的提示,直接离开了。

架构师紧急介入,做了一个决策:将库存一致性从强一致性降级为最终一致性。修改后,系统在高负载时允许库存数据短暂不一致(误差在几秒内),但始终保证页面可以正常响应。结果:双十一零点平稳度过,虽然出现了少量库存超卖(后续补偿),但系统没有崩溃。

这个决策背后,是 BASE 理论在生产环境中的真实应用。

【架构权衡】 BASE 理论是 CAP 定理的工程化落地。当强一致性(CP)的代价太高(延迟、不可用)时,BASE 提供了"可接受的折中方案":允许系统暂时不一致,但保证最终一致。这不是放弃一致性,而是在业务可接受的范围内选择一致性


一、问题背景

1.1 为什么需要 BASE 理论?

CAP 定理告诉我们:在网络分区(P)时,C 和 A 不可兼得。但 CAP 定理是一个理论模型,实际工程中我们需要更实用的指导。

CAP 的局限性:

CAP 假设:系统要么是 CP,要么是 AP
实际情况:一致性是一个连续的光谱,不是二元选择

CAP 的启示:
├─ 强一致性 + 可用性:在分区期间不可兼得
├─ 分区期间:必须选择 CP 或 AP
└─ 正常时期:C 和 A 可以兼得

BASE 的贡献:
└─ 提供了在 CP 和 AP 之间平滑过渡的工程框架

1.2 BASE 的定义

BASE = Basically Available + Soft state + Eventually consistent

┌─────────────────────────────────────────────────────────────┐
│                    基本可用(Basically Available)            │
│                                                              │
│  核心思想:系统在分区期间保证基本功能可用                      │
│                                                              │
│  实现方式:                                                  │
│  ├─ 降级:返回默认值或缓存数据                               │
│  ├─ 延迟响应:接受请求但不立即处理                           │
│  └─ 功能降级:只提供核心功能,关闭非核心功能                  │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                       软状态(Soft state)                   │
│                                                              │
│  核心思想:系统状态可以不同步,允许数据暂时不一致              │
│                                                              │
│  实现方式:                                                  │
│  ├─ 异步同步:写入时不立即同步到所有节点                     │
│  ├─ 版本向量:记录数据在各节点的版本                         │
│  └─ 冲突检测:定期检测并解决冲突                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    最终一致性(Eventually Consistent)         │
│                                                              │
│  核心思想:系统在没有新写入的情况下,最终会达到一致状态        │
│                                                              │
│  实现方式:                                                  │
│  ├─ 补偿机制:检测并修复不一致                               │
│  ├─ 重试同步:失败的操作不断重试                            │
│  └─ 版本冲突解决:使用 Last-Write-Wins、LWW 等策略          │
└─────────────────────────────────────────────────────────────┘

【架构权衡】 BASE 理论不是"放弃一致性",而是"有策略地接受暂时的不一致"。关键问题是:你的业务能接受多大的不一致窗口?能接受什么样的不一致后果?

二、方案演进

2.1 从 CAP 到 BASE 的思维转变

传统思维(CP):
├─ 数据必须一致 → 不一致 = 错误
├─ 可用性可以牺牲 → 不可用 = 故障
└─ 结果:系统在压力下崩溃

BASE 思维:
├─ 数据可以暂时不一致 → 接受并处理不一致
├─ 可用性必须保证 → 不可用 = 损失用户
└─ 结果:系统在压力下优雅降级

2.2 最终一致性的不同变体

最终一致性不是一个单一模型,而是一族模型:

1. 因果一致性(Causal Consistency)
   └─ 满足因果关系的操作必须按顺序执行
   └─ 例如:A 写入,B 读取必须能看到 A 的写入

2. 读己之所写(Read Your Writes)
   └─ 读取时能看到自己刚才的写入
   └─ 需要 session 或 token 追踪

3. 单调读一致性(Monotonic Read)
   └─ 多次读取看到的数据单调递增(不倒退)
   └─ 需要记录读取历史或使用单调路由

4. 单调写一致性(Monotonic Write)
   └─ 同一客户端的多次写入按顺序执行
   └─ 需要写操作的全局排序

5. 前缀一致性(PRAM / Read-your-writes-my-writes)
   └─ 组合了因果、读己之所写和单调读
选择指南:

场景:社交 Feed
├─ 推荐:最终一致性 + 补偿机制
├─ 原因:用户可以接受看到稍旧的 Feed
└─ 不可接受:页面打不开

场景:库存扣减
├─ 推荐:强一致性 或 悲观锁 + 最终一致性兜底
├─ 原因:超卖会导致资损
└─ 可接受:请求稍慢

场景:支付
├─ 推荐:强一致性
├─ 原因:资金必须精确
└─ 不可接受:任何不一致

三、核心设计

3.1 BASE 的工程实现

步骤一:识别核心功能和非核心功能

基本可用的设计:

核心功能(必须在分区期间保证):
├─ 用户登录(返回降级页面)
├─ 商品展示(返回缓存数据)
└─ 下单入口(可降级为"暂时无法下单")

非核心功能(可以关闭):
├─ 商品推荐
├─ 用户画像
├─ 实时评论
└─ 活动提醒

步骤二:设计软状态的处理策略

// 库存服务示例:软状态的异步同步
@Service
public class InventoryService {
    // 本地缓存(软状态)
    private Map<String, Integer> localInventory = new ConcurrentHashMap<>();

    @Async
    public void syncToRemote(String itemId, int delta) {
        // 异步同步到中央库存系统
        // 同步失败不影响本地服务
        try {
            remoteInventoryService.sync(itemId, delta);
        } catch (Exception e) {
            // 记录失败,加入重试队列
            retryQueue.add(new SyncTask(itemId, delta));
        }
    }

    public InventoryResult deduct(String itemId, int count) {
        // 本地直接扣减(快速响应)
        Integer current = localInventory.get(itemId);
        if (current == null || current < count) {
            return InventoryResult.INSUFFICIENT;
        }
        localInventory.put(itemId, current - count);

        // 异步同步
        syncToRemote(itemId, -count);

        return InventoryResult.SUCCESS;
    }
}

步骤三:设计最终一致的补偿机制

// 定时任务:检测并修复不一致
@Scheduled(fixedRate = 30000)
public void reconcileInventory() {
    // 1. 从中央库存系统拉取真实库存
    Map<String, Integer> realInventory = remoteInventoryService.getAll();

    // 2. 与本地缓存对比
    for (Map.Entry<String, Integer> entry : realInventory.entrySet()) {
        String itemId = entry.getKey();
        int real = entry.getValue();
        int local = localInventory.getOrDefault(itemId, 0);

        // 3. 如果不一致,以中央为准
        if (real != local) {
            log.warn("库存不一致,itemId={}, real={}, local={}",
                itemId, real, local);
            localInventory.put(itemId, real);
        }
    }
}

【架构权衡】 BASE 的实现核心是"接受不一致 + 设计补偿"。关键是:你要清楚地知道"不一致窗口有多大"、"不一致的数据如何修复"、"修复时用户可能看到什么"。

3.2 BASE 与 ACID 的对比

维度ACID(传统数据库)BASE(分布式系统)
一致性模型强一致性最终一致性
事务支持完整 ACID 事务无原子事务,需要补偿机制
隔离级别多种隔离级别弱隔离,通常无隔离
可用性在网络分区时不可用始终可用(基本可用)
性能低延迟(单节点)高吞吐(分布式)
扩展性垂直扩展水平扩展
适用场景金融、订单等强一致性需求互联网高并发场景

四、生产避坑

4.1 最终一致性不是"无一致性"

很多团队错误地认为 BASE = 不需要考虑一致性:

❌ 错误理解:BASE = 最终一致性 = 可以随意写
✅ 正确理解:BASE = 最终一致性 = 有策略地处理不一致

最终一致性的"最终"是多长?
├─ DynamoDB:通常 < 1 秒
├─ Cassandra:通常 < 10 秒
├─ 自定义系统:取决于同步机制
└─ 关键:必须明确 SLA 中的"最终"时间

4.2 补偿机制的设计陷阱

// ❌ 陷阱1:补偿逻辑不完整
@Async
public void processOrder(Order order) {
    try {
        deductInventory(order);    // 扣库存
        createPayment(order);      // 创建支付
        sendNotification(order);   // 发送通知
    } catch (Exception e) {
        // 缺少补偿逻辑
        log.error("处理失败", e);
    }
}

// ✅ 正确做法:完整的补偿逻辑
@Async
public void processOrder(Order order) {
    try {
        deductInventory(order);
        createPayment(order);
        sendNotification(order);
    } catch (InventoryException e) {
        // 补偿:什么都不用做,库存没扣成功
        notifyUser(order.getUserId(), "库存不足");
    } catch (PaymentException e) {
        // 补偿:回滚库存
        restoreInventory(order);
        notifyUser(order.getUserId(), "支付失败");
    } catch (NotificationException e) {
        // 补偿:记录日志,后续重试
        // 通知失败通常可以接受
        log.warn("通知发送失败", e);
    }
}

4.3 幂等性是 BASE 系统的基础

// 幂等性是处理重试和补偿的基础
@Service
public class OrderService {

    // 幂等键存储(Redis 或数据库)
    @Autowired
    private IdempotencyStore idempotencyStore;

    public Order createOrder(String idempotencyKey, OrderRequest request) {
        // 1. 检查是否已处理
        if (idempotencyStore.contains(idempotencyKey)) {
            return idempotencyStore.get(idempotencyKey);
        }

        // 2. 处理订单
        Order order = doCreateOrder(request);

        // 3. 存储结果
        idempotencyStore.put(idempotencyKey, order);

        return order;
    }
}

五、工程代价评估

维度评估
实现复杂度高(需要补偿机制、幂等性、冲突解决)
运维成本高(需要监控不一致、触发补偿)
排障复杂度高(不一致问题难以复现和排查)
数据修复必要且关键
用户体验好(始终可用)
业务风险需要评估不一致的代价

六、落地 Checklist

  • 业务分析:识别哪些数据必须强一致,哪些可以最终一致
  • SLA 定义:明确"最终"的精确时间窗口
  • 补偿设计:为所有可能的不一致设计补偿机制
  • 幂等设计:所有写操作必须幂等
  • 监控部署:监控数据不一致率和补偿触发率
  • 数据修复:设计并实现定期数据对账机制
  • 测试验证:在压测中验证降级行为和恢复流程