跳到主要内容

CAP 定理与 BASE 理论

CAP 定理和 BASE 理论是分布式系统设计的理论基础。理解这两个理论,能帮助我们在一致性和可用性之间做出明智的权衡。

CAP 定理

定理的起源与定义

CAP 定理由加州大学伯克利分校的 Eric Brewer 教授在 2000 年的 ACM Symposium on Principles of Distributed Computing 上首次提出。当时, Brewer 观察到新兴的 Web 服务在设计分布式系统时面临一个根本性的困境:在网络分区发生时,无法同时保证一致性和可用性。2002 年,MIT 的 Seth Gilbert 和 Nancy Lynch 在论文《Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services》中正式证明了这一定理。

CAP 定理指出:在一个分布式系统中,当发生网络分区时,无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三个特性中的全部三个,最多只能同时满足其中两个。

需要注意的是,CAP 定理描述的是一个"不可能性结果"——它告诉我们什么做不到,而不是应该怎么做。这个定理的重要性在于,它帮助设计师认识到必须做出取舍,而不是幻想能同时拥有所有好处。

三个特性的精确含义

理解 CAP 定理的关键在于准确理解每个特性的含义。很多人对 CAP 存在误解,正是因为对这三个特性的定义不够精确。

一致性(Consistency)

CAP 定理中的一致性指的是线性一致性(Linearizability),也称为强一致性或原子一致性。这是一个非常强的一致性保证。

线性一致性的精确定义是:所有操作看起来像是在一个单一的时间点上原子地执行,每个操作都在其调用和响应之间的某个时刻生效。换句话说,系统表现得就像只有一份数据副本,所有操作都按某种全局顺序执行。

具体来说,线性一致性要求:

  1. 原子性:操作要么完全成功,要么完全失败,不存在中间状态
  2. 实时性:操作的效果在响应返回之前对所有客户端可见
  3. 顺序性:如果操作 A 在操作 B 开始之前完成,那么所有客户端看到的顺序都是 A 先于 B
// 线性一致性的直觉理解
// 假设有一个分布式寄存器,初始值为 0

// 时刻 t1: 客户端 A 写入 x=1
// 时刻 t2: 客户端 B 写入 x=2
// 时刻 t3: 客户端 C 读取 x

// 线性一致性要求:
// 如果 t1 < t2 < t3,那么 C 必须读到 x=2
// 即使 A 和 B 的写操作在不同的节点上

class LinearizableRegister {
// 要实现线性一致性,必须在多数派节点上完成写入
// 才能认为写入成功

synchronized boolean write(int value) {
int successCount = 0;
for (Node node : allNodes) {
if (node.syncWrite(value)) {
successCount++;
}
}
return successCount >= majority;
}

synchronized Integer read() {
// 必须从多数派节点读取,并返回最新的值
Map<Integer, Integer> valueCount = new HashMap<>();
for (Node node : majorityNodes) {
int value = node.read();
valueCount.merge(value, 1, Integer::sum);
}
// 返回出现次数最多的值
return valueCount.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
}
}

值得注意的是,CAP 中的"C"与 ACID 事务中的"C"(一致性)含义不同。ACID 的一致性指的是数据库从一个一致状态转换到另一个一致状态,涉及业务规则的约束。而 CAP 的一致性特指线性一致性。

可用性(Availability)

CAP 定理中的可用性定义为:系统中非故障节点收到的每个请求都必须产生响应(不一定是正确的响应,但必须有响应),而且响应必须在有限时间内返回。

这个定义的关键在于:

  1. 每个请求都有响应:不区分请求类型,读请求和写请求都要响应
  2. 响应必须在有限时间:不能无限期等待
  3. 不需要区分正确与否:可以返回错误,但不能不响应
// 可用性的实现
class AvailableService {

// 高可用系统:任何存活的节点都能响应请求
String read(String key) {
// 尝试从任意存活节点读取
for (Node node : nodes) {
if (node.isAlive()) {
try {
// 不等待多数派,本地数据即可返回
return node.localRead(key);
} catch (Exception e) {
// 节点故障,继续尝试下一个
continue;
}
}
}
// 只有所有节点都故障时才返回错误
throw new ServiceUnavailableException();
}

String write(String key, String value) {
// 为了可用性,只要有一个节点写入成功即可返回
for (Node node : nodes) {
if (node.isAlive()) {
try {
node.localWrite(key, value);
return "OK"; // 不等待复制完成
} catch (Exception e) {
continue;
}
}
}
throw new ServiceUnavailableException();
}
}

这里有一个容易混淆的概念:CAP 的可用性与我们常说的"系统可用性"(如 99.99% 可用)不是一回事。后者是一个统计概念,表示系统正常运行时间的比例;而 CAP 的可用性是一个严格的理论定义,要求"每个请求都必须响应"。

分区容错性(Partition Tolerance)

分区容错性的定义是:系统在网络发生分区(部分节点之间无法通信)的情况下,仍然能够继续运行。

网络分区是指系统被分割成多个不连通的区域。例如,一个集群分布在两个数据中心,如果两个数据中心之间的网络中断,就形成了分区。每个分区内的节点可以互相通信,但跨分区的节点无法通信。

分区容错性要求系统在网络分区发生时,各个分区仍然能够独立处理请求,而不是整体停止服务。在分布式系统中,网络分区是必须考虑的现实情况——网络设备和链路总是可能出现故障的。因此,P 通常不是一个可选的属性,而是分布式系统的固有约束。

分区容错性的核心挑战在于:分区期间,不同分区可能独立地接受写请求,导致数据状态不一致。当分区恢复后,如何合并这些不一致的状态?

CAP 的权衡选择

CAP 定理告诉我们,在网络分区发生时,必须在 C(一致性)和 A(可用性)之间选择。这不是一个二选一的永久决定,而是在分区发生时的临时选择。

CP 系统:一致性优先

选择 CP 意味着在网络分区时,优先保证一致性,牺牲可用性。当一个分区无法与多数派通信时,它会拒绝服务,以避免数据不一致。

典型的 CP 系统包括:

ZooKeeper:分布式协调服务。ZooKeeper 要求写入必须在多数派节点上成功。如果某个分区无法联系到多数派,它将拒绝写入请求,甚至拒绝读取请求(如果需要强一致性的话)。ZooKeeper 的设计优先保证数据一致性,即使牺牲可用性。

etcd:分布式键值存储。etcd 使用 Raft 协议实现共识,同样要求多数派才能处理请求。分区时,少数派分区会停止服务。

HBase:分布式列式数据库。HBase 依赖 HDFS 进行数据存储,强一致性是其核心特性。

CP 系统适合对数据正确性要求极高的场景,如配置管理、分布式锁、主键生成等。即使短暂不可用,也不能接受错误数据。

AP 系统:可用性优先

选择 AP 意味着在网络分区时,优先保证可用性,接受数据不一致。每个分区独立处理请求,当分区恢复后,再进行数据同步。

典型的 AP 系统包括:

Cassandra:分布式 NoSQL 数据库。Cassandra 采用无主复制架构,写入和读取只需要满足可配置的 Quorum。即使无法联系到多数节点,只要有一个节点存活,就能继续服务。数据不一致通过读修复和反熵机制最终收敛。

DynamoDB:Amazon 的云原生 NoSQL 数据库。DynamoDB 允许配置一致性级别,默认提供最终一致读取,也可选择强一致读取(但延迟更高)。分区时优先保证可用性。

Riak:基于 Amazon Dynamo 论文实现的分布式数据库。同样是 AP 系统,优先保证可用性。

AP 系统适合对可用性要求高、能容忍短暂不一致的场景,如社交媒体、内容管理、日志收集等。

对 CAP 定理的常见误解

CAP 定理经常被误解和滥用。让我们澄清一些常见的误解。

误解一:CAP 意味着系统只能选两个属性

这是一个过于简化的理解。CAP 定理说的是"在发生网络分区时",才需要在 C 和 A 之间选择。在网络正常的情况下,系统可以同时满足 CAP 三个属性。

实际上,大多数分布式系统在正常情况下都是 CA 的——同时提供一致性和可用性。只有当网络分区发生时,才需要做出选择。

误解二:CAP 是二选一的永久选择

CAP 的选择不是永久的。一个系统可以在不同操作上选择不同的策略。例如,对于关键数据(如账户余额)选择 CP,对于非关键数据(如用户头像)选择 AP。

甚至对于同一个操作,也可以根据当前系统状态动态选择。Netflix 的 Chaos Monkey 工具就是用来测试系统在各种故障场景下的表现,确保 CAP 选择符合预期。

误解三:AP 系统完全不保证一致性

AP 系统并非完全不关心一致性,而是选择"最终一致性"而非"强一致性"。最终一致性意味着,在没有新更新的情况下,所有副本最终会收敛到相同的值。在收敛之前,不同客户端可能看到不同的值,但这种不一致是暂时的。

误解四:CP 系统在网络分区时完全不可用

CP 系统只是拒绝可能导致不一致的请求,并不一定完全不可用。例如,分区中的多数派分区仍然可以正常服务。只有少数派分区才会拒绝请求。而且,只读请求在多数派分区通常仍然可以处理。

Martin Kleppmann 的批评

Martin Kleppmann(DDIA 作者)在 2015 年发表了《A Critique of the CAP Theorem》论文,对 CAP 定理提出了深刻的批评。

Kleppmann 指出,CAP 定理的问题在于它过于简化了现实。在实践中,网络分区并非唯一需要权衡的因素。在正常情况下(无分区时),我们同样需要在一致性和延迟之间权衡。

CAP 定理将一致性和可用性呈现为非此即彼的选择,但实际上这是一个谱系。系统可以选择不同级别的一致性:

  • 线性一致性(最强)
  • 顺序一致性
  • 因果一致性
  • 最终一致性(最弱)

每种一致性级别都有其适用场景和性能特点。同样,可用性也不是二元的,而是可以在不同程度上满足。

Kleppmann 建议放弃对 CAP 定理的过度关注,转而关注更实用的概念,如延迟与一致性的权衡、故障模式的设计等。这催生了 PACELC 定理。

PACELC 定理

PACELC 定理是对 CAP 的扩展,由 Daniel Abadi 在 2010 年提出。它的核心思想是:CAP 定理只考虑了网络分区的情况,但即使在正常情况下,也需要在延迟和一致性之间权衡。

PACELC 的命名源于:

如果发生分区(P),系统在可用性(A)和一致性(C)之间选择;
否则(E),系统在延迟(L)和一致性(C)之间权衡。

PACELC 揭示了一个重要事实:即使是 CP 系统,在正常情况下也要在延迟和一致性之间权衡。如果要求强一致性,就需要等待多数派确认,这会增加延迟。如果希望低延迟,可能需要接受较弱的一致性。

这个定理帮助我们从更全面的视角审视分布式系统的设计权衡,而不仅仅局限于 CAP 的框架。

BASE 理论

BASE 理论是对 CAP 定理中 AP 系统设计思想的进一步阐述,它提供了一套实用的指导原则,帮助我们在接受最终一致性的前提下构建高可用的分布式系统。BASE 一词由 eBay 架构师 Dan Pritchett 在 2008 年的 ACM Queue 文章中提出,用以对比传统数据库的 ACID 特性。

BASE 的三个要素

BA:Basically Available(基本可用)

基本可用强调的是系统在出现故障时,保证核心功能可用,而非全部功能可用。这是一种务实的妥协——当系统面临压力或故障时,通过有策略地放弃部分非核心功能,确保核心业务不受影响。

基本可用的典型实现方式包括:

  • 降级(Degradation):关闭或简化非核心功能。例如,电商大促期间关闭商品评论功能,只保留浏览和下单功能。
  • 限流(Rate Limiting):拒绝超出处理能力的请求,保证已接受的请求能够正常处理。
  • 延迟服务:响应可能比正常情况更慢,但仍然返回结果。例如,在高峰期查询结果可能需要几秒而非几十毫秒。
// 基本可用示例:电商大促期间的降级策略
public class SeckillService {

private volatile boolean isHighLoad = false;

// 核心功能:下单
public OrderResult createOrder(OrderRequest request) {
// 核心功能始终正常提供
return orderService.create(request);
}

// 非核心功能:商品推荐
public List<Product> getRecommendations(String userId) {
if (isHighLoad) {
// 高负载时返回默认推荐,而非个性化推荐
return getDefaultRecommendations();
}
return recommendationService.getPersonalized(userId);
}

// 非核心功能:商品评论
public List<Comment> getComments(String productId) {
if (isHighLoad) {
// 高负载时关闭评论功能
throw new ServiceDegradedException("评论功能暂时关闭");
}
return commentService.getByProduct(productId);
}
}

S:Soft State(软状态)

软状态是指系统中的数据状态可以是中间状态,且中间状态的存在不会影响系统的整体可用性。与硬状态不同,软状态允许数据在不同节点间存在短暂的不一致。

软状态的核心思想是:接受状态的不确定性,允许系统在一段时间内存在中间状态,最终通过某种机制达到一致。这种设计大大降低了系统对同步的依赖,提高了性能和可用性。

典型的软状态场景:

  • 数据库主从复制:主库写入后,从库可能短暂落后,存在复制延迟。这期间数据处于软状态。
  • 缓存更新:缓存失效后重新加载,加载期间可能返回旧数据或空数据,这也是软状态。
  • 分布式计数:不同节点的计数器可能不同步,但通过后台合并最终一致。
// 软状态示例:社交动态的点赞计数
public class LikeCounter {

// 本地计数器(软状态)
private AtomicLong localCount = new AtomicLong(0);

// 定期与全局计数器同步
@Scheduled(fixedRate = 5000)
public void syncWithGlobal() {
// 将本地增量同步到全局计数器
long delta = localCount.getAndSet(0);
globalCounter.addAndGet(delta);
}

// 点赞操作:立即更新本地计数
public void like(String postId) {
localCount.incrementAndGet();
// 不等待同步,直接返回成功
}

// 获取点赞数:可能返回稍微过时的数据
public long getLikeCount(String postId) {
return globalCounter.get() + localCount.get();
}
}

E:Eventually Consistent(最终一致性)

最终一致性是软状态的延续和目标。它承诺:在没有新的更新的情况下,经过一段时间后,所有副本最终会达到一致的状态。这个"一段时间"是多久取决于系统设计,可能是毫秒级,也可能是秒级甚至分钟级。

最终一致性不意味着数据一定会一致,而是说在没有持续更新的情况下,数据会收敛到一致状态。如果更新持续发生,系统可能永远处于不一致状态,但每个不一致都是暂时的。

最终一致性是一个谱系,包含多种不同强度的一致性模型:

一致性模型描述典型应用
因果一致性有因果关系的操作顺序一致,无因果的操作可以乱序社交网络(评论必须在其父帖子之后)
读己之写客户端总能读到自己的写入,但可能读不到别人的写入个人设置、购物车
单调读客户端读到某个值后,后续读取不会返回更旧的值新闻列表、消息记录
单调写同一客户端的写入按顺序被其他客户端看到日志系统
会话一致性在同一会话内保证单调读和读己之写Web 会话
// 最终一致性的实现:版本向量
public class VersionedValue {
private String value;
private Map<String, Long> versionVector; // 节点ID -> 版本号

// 合并两个版本:取每个节点的最大版本号
public void merge(VersionedValue other) {
for (Map.Entry<String, Long> entry : other.versionVector.entrySet()) {
Long current = versionVector.get(entry.getKey());
if (current == null || current < entry.getValue()) {
versionVector.put(entry.getKey(), entry.getValue());
// 如果 other 更新,可能需要更新 value
}
}
}

// 判断是否发生冲突
public boolean isConflict(VersionedValue other) {
// 如果两个版本互不包含,则发生冲突
boolean thisDominates = dominates(this.versionVector, other.versionVector);
boolean otherDominates = dominates(other.versionVector, this.versionVector);
return !thisDominates && !otherDominates;
}

private boolean dominates(Map<String, Long> v1, Map<String, Long> v2) {
// v1 是否在所有分量上都大于等于 v2,且至少有一个分量严格大于
boolean hasStrictlyGreater = false;
for (Map.Entry<String, Long> entry : v1.entrySet()) {
Long v2Value = v2.get(entry.getKey());
if (v2Value == null || entry.getValue() > v2Value) {
hasStrictlyGreater = true;
} else if (entry.getValue() < v2Value) {
return false;
}
}
return hasStrictlyGreater;
}
}

最终一致性的实现机制

要实现最终一致性,系统需要解决两个问题:如何检测不一致,以及如何修复不一致。

读修复(Read Repair)

当客户端读取数据时,系统会检查各副本的一致性。如果发现不一致,立即触发修复。这种方式适合读多写少的场景,修复成本分摊到读取操作中。

反熵(Anti-Entropy)

后台进程定期比较各副本的数据,检测并修复不一致。反熵通常使用 Merkle Tree 来高效地比较大量数据。Cassandra 采用这种方式。

提示移交(Hinted Handoff)

当写入操作的目标节点不可达时,协调节点会暂存写入请求(称为 hint)。当目标节点恢复后,协调节点将暂存的写入发送给它。这种方式可以减少网络分区期间的数据不一致。

BASE vs ACID

BASE 和 ACID 代表了两种截然不同的设计哲学。ACID 追求强一致性,不惜牺牲性能和可用性;BASE 接受最终一致性,换取高性能和高可用。两者的对比可以帮助我们更好地理解它们的适用场景。

特性ACIDBASE
一致性强一致性:事务完成后,所有节点立即看到相同数据最终一致性:各节点最终收敛到一致状态
事务模型严格的事务边界,要么全成功要么全失败柔性事务,通过补偿机制处理失败
隔离性通过锁机制保证事务隔离接受脏读、不可重复读
持久性一旦提交,数据永久保存可能丢失最近的更新
可用性单点故障导致整体不可用部分故障不影响整体可用性
性能受限于同步协调开销可以线性扩展
复杂度数据库层面保证,应用简单应用层面处理补偿,复杂度高
典型应用金融交易、库存管理社交媒体、内容分发

ACID 和 BASE 并非绝对对立。在实践中,许多系统采用混合策略:对关键数据使用 ACID,对非关键数据使用 BASE。例如,电商系统中,订单和支付使用强一致性,而商品浏览记录使用最终一致性。

实践:如何做出选择

理论最终要服务于实践。当我们设计分布式系统时,如何根据业务场景选择合适的一致性和可用性策略?

决策框架

选择的一致性级别取决于业务对不一致的容忍度。我们可以从以下几个维度来分析:

数据价值:数据越重要,对一致性的要求越高。用户账户余额、交易记录等必须保证一致性;而浏览记录、点击计数等可以接受最终一致性。

业务容忍度:业务能容忍多长时间的不一致?秒级、分钟级还是小时级?容忍度越高,可以采用越弱的一致性模型。

用户体验:强一致性带来的延迟是否影响用户体验?如果用户需要等待几秒才能看到自己的操作结果,体验会大打折扣。

系统规模:系统规模越大,强一致性的代价越高。跨数据中心的强一致性需要数百毫秒的延迟。

典型场景分析

金融交易:强一致性优先

金融系统对数据一致性有极高要求。账户余额、交易记录不能出错,宁可短暂不可用也不能接受错误数据。

银行转账是典型的例子。从账户 A 转账到账户 B,必须保证原子性:要么两边都成功,要么都失败,不能出现 A 扣了钱但 B 没收到的情况。

// 金融转账:强一致性实现
public class BankTransfer {

private final DistributedLock lock;
private final AccountRepository accountRepo;

@Transactional
public TransferResult transfer(String fromAccount, String toAccount,
BigDecimal amount) {
// 1. 按固定顺序加锁,避免死锁
String lock1 = fromAccount.compareTo(toAccount) < 0 ? fromAccount : toAccount;
String lock2 = fromAccount.compareTo(toAccount) < 0 ? toAccount : fromAccount;

lock.acquire(lock1);
try {
lock.acquire(lock2);
try {
// 2. 检查余额
Account from = accountRepo.findById(fromAccount);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}

// 3. 执行转账(本地事务)
accountRepo.debit(fromAccount, amount);
accountRepo.credit(toAccount, amount);

// 4. 记录交易日志
transactionLog.append(new Transaction(fromAccount, toAccount, amount));

return TransferResult.success();
} finally {
lock.release(lock2);
}
} finally {
lock.release(lock1);
}
}
}

金融系统通常采用以下策略:

  • 使用传统关系数据库的事务机制
  • 采用 Paxos/Raft 共识协议保证一致性
  • 在网络分区时优先保证一致性,暂停服务

社交媒体:可用性优先

社交媒体的用户基数大,对可用性要求高。用户发一条微博,短暂不一致是可以接受的——部分粉丝晚几秒看到,影响不大。但如果系统不可用,用户无法发博,体验会大打折扣。

// 社交媒体发布:可用性优先
public class SocialPostService {

private final LocalStorage localStorage;
private final MessageQueue messageQueue;

// 发布内容
public PostResult post(String userId, String content) {
// 1. 立即写入本地存储
Post post = new Post(userId, content, System.currentTimeMillis());
localStorage.save(post);

// 2. 异步分发到粉丝的时间线
// 不等待分发完成,立即返回成功
messageQueue.publish(new PostEvent(post));

return PostResult.success(post.getId());
}

// 获取时间线:可能返回稍微过时的数据
public List<Post> getTimeline(String userId) {
// 优先从本地缓存读取
List<Post> cached = localStorage.getTimeline(userId);
if (!cached.isEmpty()) {
return cached;
}

// 缓存为空,从远程获取
return fetchFromRemote(userId);
}
}

社交媒体系统通常采用以下策略:

  • 使用 Cassandra 等支持最终一致性的数据库
  • 通过消息队列异步分发内容
  • 本地优先写入,后台同步

电商库存:混合策略

电商系统的库存扣减是一个有趣的场景。它需要在一对矛盾中平衡:既要避免超卖(卖出的商品超过库存),又要保证用户体验(不要明明有库存却显示无货)。

库存扣减采用强一致性,因为超卖会导致严重的业务问题——发货时发现没货,需要取消订单并退款,用户体验极差。但库存展示可以采用最终一致性,轻微的不准确不影响用户体验。

// 电商库存:混合策略
public class InventoryService {

private final DistributedLock lock;
private final InventoryRepository repo;
private final Cache cache;

// 库存扣减:强一致性
public boolean deduct(String productId, int quantity) {
// 使用分布式锁保证强一致
String lockKey = "inventory:" + productId;
lock.acquire(lockKey);
try {
Inventory inventory = repo.findById(productId);
if (inventory.getAvailable() < quantity) {
return false;
}

repo.decreaseAvailable(productId, quantity);

// 异步更新缓存
cache.asyncUpdate(productId, inventory.getAvailable() - quantity);

return true;
} finally {
lock.release(lockKey);
}
}

// 库存查询:最终一致性(允许短暂不一致)
public int getAvailable(String productId) {
// 先查缓存
Integer cached = cache.get(productId);
if (cached != null) {
return cached;
}

// 缓存未命中,查数据库
Inventory inventory = repo.findById(productId);
cache.set(productId, inventory.getAvailable());
return inventory.getAvailable();
}

// 库存预热:定期同步缓存
@Scheduled(fixedRate = 60000)
public void warmUpCache() {
List<String> hotProducts = getHotProducts();
for (String productId : hotProducts) {
Inventory inventory = repo.findById(productId);
cache.set(productId, inventory.getAvailable());
}
}
}

决策流程

当面对一个具体的分布式系统设计决策时,可以按照以下流程思考:

  1. 分析业务需求:对一致性和可用性分别有多高的要求?
  2. 识别关键路径:哪些操作必须强一致,哪些可以弱一致?
  3. 设计数据模型:根据一致性需求选择合适的数据存储
  4. 考虑故障场景:当网络分区发生时,系统应该如何响应?
  5. 设计补偿机制:如果发生不一致,如何检测和修复?

小结

CAP 定理和 BASE 理论是分布式系统设计的理论基石。本章我们从多个角度深入探讨了这两个理论:

CAP 定理告诉我们,当网络分区发生时,必须在一致性和可用性之间选择。这不是一个永远的决定,而是在特定情况下的权衡。理解 CAP 定理的关键是准确理解一致性(线性一致性)、可用性(每个请求都有响应)、分区容错性(分区时仍能运行)的精确定义。

PACELC 定理扩展了 CAP 的视角,指出即使在正常情况下,我们也需要在延迟和一致性之间权衡。这帮助我们理解为什么强一致性系统(如 ZooKeeper)在正常情况下也可能有较高的延迟。

BASE 理论提供了一套实用的设计指南:基本可用(保证核心功能)、软状态(接受中间状态)、最终一致性(最终收敛到一致)。BASE 是 CAP 中 AP 系统的具体实践方法。

在实践中,大多数系统采用混合策略:根据数据的重要性和业务需求,对不同数据采用不同的一致性级别。没有银弹,只有适合特定场景的选择。

如果你想深入理解这些理论,推荐阅读:

  • 《Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services》by Gilbert & Lynch——CAP 定理的原始证明
  • 《A Critique of the CAP Theorem》by Martin Kleppmann——对 CAP 定理的深刻反思
  • 《Base: An Acid Alternative》by Dan Pritchett——BASE 理论的原始文章
  • 《Designing Data-Intensive Applications》by Martin Kleppmann——全面讲解数据系统的权衡