分布式系统CAP理论:供应链业务中的一致性取舍

CAP 理论是理解分布式系统取舍的基础。它经常被概括为“一致性、可用性、分区容错性三选二”,这个说法便于记忆但不够严谨,也容易在真实系统设计中被误用。

更实用的理解是:当系统出现网络分区时,分布式系统必须在一致性和可用性之间做取舍。平时网络正常时,一个系统可以同时做到比较好的一致性和可用性;真正考验架构选择的,是网络异常、节点超时、跨机房通信失败、消息延迟这些情况。

CAP 在供应链系统中的取舍流程

CAP 分别是什么意思

CAP 包含三个概念。

Consistency,一致性,在 CAP 语境下接近线性一致性:一次写入成功后,后续读取应表现得像访问同一份按顺序更新的数据,不能从其他节点读到更旧的值。它不同于数据库 ACID 中的 Consistency。

Availability,可用性,指每个非故障节点都能在合理时间内返回响应。注意这里强调的是返回响应,不代表一定返回最新数据。

Partition Tolerance,分区容错性,指系统在网络分区发生时仍然能够继续运行。网络分区可以理解为节点之间无法通信,或者通信延迟大到系统认为对方已经不可用。

在单机系统里,不需要讨论 P,因为没有跨节点通信。但 Java 分布式系统天然存在多个进程、多个节点、多个数据库副本、多个机房,P 几乎不可避免。只要系统是分布式的,就必须默认网络会出问题。

为什么不是简单的三选二

CAP 经常被简化成 C、A、P 三选二,但工程上更准确的说法是:在发生网络分区时,如果继续对外提供服务,就可能牺牲强一致性;如果坚持强一致性,就可能拒绝部分请求,从而牺牲可用性。

也就是说,P 不是一个可以随便放弃的选项。对于真实分布式系统,网络分区一定要考虑。选择更多发生在 CP 和 AP 之间。

CP 系统优先保证一致性。网络异常时,它宁可让部分请求失败,也不返回可能错误的数据。

AP 系统优先保证可用性。网络异常时,它允许部分节点继续提供服务,但可能出现短时间数据不一致,后续通过补偿、同步、校验来恢复一致。

供应链业务里的库存扣减例子

假设有一个供应链系统,核心业务链路是:客户下单、锁定库存、生成出库任务、仓库拣货、物流发货。

其中库存是最容易体现 CAP 取舍的对象。系统有两个机房:上海机房和北京机房。为了提高访问速度,两个机房都部署了订单服务和库存服务,并且都有库存数据副本。

现在有一个 SKU:A1001,可售库存只剩 1 件。上海客户和北京客户几乎同时下单。

如果系统选择强一致性,库存扣减必须由能够形成单一提交顺序的权威节点或共识协议确认。分布式锁只有在具备正确的租约、故障转移和陈旧请求隔离机制时才能参与这一过程,不能仅凭“加了一把锁”就宣称获得强一致性。只有一个订单能成功锁定库存,另一个订单会失败或进入等待。这样可以避免并发超卖,但如果上海和北京之间网络断开,无法确认权威库存的一侧就要拒绝下单或提示系统繁忙。

这就是 CP 取舍:宁可不可用,也不能卖出不存在的库存。

如果系统选择高可用,上海机房和北京机房可以在网络分区时各自接单。两个机房都认为库存还有 1 件,于是两个订单都成功。等网络恢复后,系统发现库存被多扣了,需要做补偿:取消后下单的订单、通知客服、给用户补偿券,或者从其他仓库调拨。

这就是 AP 取舍:先保证用户能下单,再通过业务补偿处理不一致。

哪些供应链场景更适合 CP

不是所有业务都需要强一致性,但有些场景不应该轻易牺牲一致性。

第一类是库存最后一件商品的锁定。对于稀缺商品、定制件、批次严格的物料,如果超卖会带来严重履约风险,库存锁定应该偏 CP。

第二类是财务结算。供应商应付、客户应收、账期核销、发票金额,这些数据一旦错了,后续修正成本很高,甚至会影响审计。财务主账通常要选择强一致或准强一致。

第三类是审批状态。采购订单从待审到已审、已驳回、已作废,状态流转必须清晰。不能一个节点显示已审批,另一个节点还允许继续修改明细。

第四类是唯一性约束。例如采购单号、出库单号、批次号,不能因为网络分区在两个节点生成重复编号。

这些场景的共同点是:错误数据比短暂不可用更可怕。系统可以提示稍后重试,但不能让错误状态进入主流程。

哪些供应链场景可以偏 AP

也有很多场景更适合优先保证可用性。

第一类是商品基础信息展示。商品名称、图片、描述、类目、品牌等信息短时间不一致,通常不会造成严重后果。读多写少的数据可以通过缓存和异步同步提升可用性。

第二类是报表统计。当天订单量、仓库作业量、供应商履约率这些指标允许分钟级延迟。报表更关心趋势和分析,不一定要求每一次刷新都读到最新事务数据。

第三类是搜索索引。订单搜索、商品搜索、供应商搜索往往使用 Elasticsearch 等搜索引擎,索引延迟几秒或几十秒通常可以接受。

第四类是消息通知。订单创建后发送短信、站内信、邮件,如果短时间失败,可以重试或补发,不应该阻塞主交易链路。

这些场景的共同点是:短时间不一致可以被用户接受,系统可以通过最终一致性修复。

Java 系统里常见的 CAP 落地方式

在 Java 分布式系统里,CAP 不是只体现在数据库选型上,也体现在服务设计、缓存策略、消息机制和降级策略里。

对于偏 CP 的场景,单库内可以依赖事务、唯一索引、条件更新和行锁建立确定的提交顺序;跨节点则需要理解所用数据库或协调组件的复制与共识语义,例如基于 Raft 的多数派提交。普通异步主从复制不能自动等同于强一致。比如库存锁定时以 sku_id + warehouse_id 唯一标识库存行,通过带可用量条件的原子更新完成扣减,避免并发请求把库存扣成负数。

对于偏 AP 的场景,可以使用缓存、消息队列、异步任务、重试补偿、定时对账、事件溯源、最终一致性表等方式。比如订单创建后发送库存变更事件,报表系统异步消费事件生成统计结果。

关键是不要把所有业务都设计成一种取舍。供应链系统里,库存、财务、审批偏一致性;报表、搜索、通知偏可用性;订单主流程则通常是分段取舍:核心状态强一致,周边动作最终一致。

一个简单的 Java 策略 Demo

在代码里可以把不同业务能力的 CAP 取舍显式表达出来,避免所有服务都套同一种降级逻辑:

1
2
3
4
5
6
7
8
9
10
11
public enum ConsistencyMode {
CP_REQUIRED, // 失败就拒绝,不能返回可能错误的数据
AP_ACCEPTABLE // 先返回可用结果,后续通过补偿修正
}

public record CapabilityPolicy(
String capability,
ConsistencyMode mode,
Duration timeout
) {
}

库存锁定可以配置成偏 CP:

1
2
3
4
5
CapabilityPolicy reserveInventory = new CapabilityPolicy(
"reserve-inventory",
ConsistencyMode.CP_REQUIRED,
Duration.ofMillis(800)
);

消息通知可以配置成偏 AP:

1
2
3
4
5
CapabilityPolicy notifyCustomer = new CapabilityPolicy(
"notify-customer",
ConsistencyMode.AP_ACCEPTABLE,
Duration.ofMillis(200)
);

调用方根据策略决定失败处理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public OrderResult createOrder(CreateOrderCommand command) {
inventoryService.reserve(command.skuId(), command.qty()); // CP:失败则订单不能创建

Order order = orderRepository.save(command.toOrder());

try {
notificationService.sendOrderCreated(order); // AP:失败不阻塞主链路
} catch (Exception ex) {
outboxRepository.save(NotificationEvent.from(order)); // 后续重试补偿
}

return OrderResult.success(order.getOrderNo());
}

这段代码表达了一个重要原则:不是所有异常都应该用同一种方式处理。库存锁定失败会影响主交易正确性,所以必须失败返回;通知失败只影响用户触达,可以通过 outbox、消息队列和定时任务补发。

一个完整的订单链路取舍

可以把下单流程拆成几个步骤:

  1. 创建订单草稿。
  2. 校验客户、价格、合同。
  3. 锁定库存。
  4. 写入订单主表。
  5. 发送订单创建事件。
  6. 生成出库任务。
  7. 通知客户下单成功。

其中第 2、3、4 步属于主交易链路,应该更重视一致性。第 5、6、7 步可以更多使用消息和补偿,保证主链路响应速度和可用性。

如果库存服务暂时不可用,系统可以拒绝创建正式订单,保留草稿并提示稍后重试。如果短信服务不可用,订单仍然可以创建成功,短信后续重试。这就是按业务重要性拆分 CAP 取舍。

监控和补偿不能省略

偏 AP 的系统不能只说“最终一致”,必须设计最终怎么一致。至少需要三类能力:

  1. 幂等:同一个订单事件重复消费时,不能重复生成出库任务或重复扣减库存。
  2. 对账:定时比较订单、库存流水、出库任务之间的状态差异。
  3. 告警:补偿失败次数、消息堆积时间、库存差异数量超过阈值时要通知研发和业务。

例如订单创建事件已经发送,但出库任务生成失败,系统应该能通过对账任务发现“已支付订单没有出库任务”,并自动补偿或进入人工处理队列。没有这些能力,AP 设计就会从“短暂不一致”变成“永久脏数据”。

总结

CAP 理论的价值不是让我们背诵一致性、可用性、分区容错性,而是提醒我们:分布式系统一定会遇到网络异常,异常发生时必须提前决定哪些数据不能错,哪些流程不能停。

供应链系统的设计原则可以总结成一句话:核心交易数据宁可慢一点,也不要错;辅助查询和通知可以快一点,再通过最终一致性修正。

真正成熟的 Java 分布式系统,不会简单宣称自己是 CP 或 AP,而是会在不同业务边界上做不同取舍,并且把补偿、对账、幂等和监控一起设计进去。