跳到主要内容

分布式系统CAP定理与BASE理论完全指南

一、为什么需要CAP和BASE理论?

1.1 分布式系统的两难选择

在单体应用时代,我们不需要考虑这些问题,因为:

  • 所有代码在一个进程里
  • 共享同一个数据库
  • 事务由本地数据库保证(ACID)

但在微服务架构中,情况完全不同:

用户下单

订单服务(订单数据库)

库存服务(库存数据库)

支付服务(支付数据库)

问题来了

  1. 如果订单服务创建成功,但库存服务扣减失败,怎么办?
  2. 如果网络分区了,服务之间无法通信,怎么办?
  3. 如果要求数据强一致性,性能会受影响吗?

1.2 CAP和BASE理论的诞生

为了解决分布式系统的设计难题,计算机科学家提出了两个重要理论:

  • CAP定理:告诉我们在分布式系统中,只能三选二
  • BASE理论:告诉我们在实践中,如何权衡和取舍

💡 面试重点:CAP和BASE理论的关系是什么?

  • CAP定理是理论限制,告诉我们不能同时满足三个属性
  • BASE理论是实践指导,告诉我们如何在CAP限制下设计系统
  • BASE是对CAP中"AP"方案的延伸和补充

二、CAP定理详解

2.1 什么是CAP定理?

CAP定理指出:一个分布式系统最多只能同时满足以下三个属性中的两个

┌─────────────────────────────────────┐
│ │
│ 分布式系统的三难选择 │
│ │
│ C (Consistency) - 一致性 │
│ A (Availability) - 可用性 │
│ P (Partition Tolerance) - 分区容错性 │
│ │
│ 只能三选二! │
│ │
└─────────────────────────────────────┘

2.2 三大核心概念

2.2.1 C - Consistency(一致性)

定义:在分布式系统中的所有数据备份,在同一时刻是否同样的值。

通俗理解

  • 强一致性:任何时候,任何节点看到的数据都是一样的
  • 就像你在银行转账,无论从哪个ATM查询,看到的余额都应该相同

示例

用户A修改了数据:
节点1(北京): username = "zhangsan"
节点2(上海): username = "zhangsan" ← 必须立即同步

无论访问哪个节点,看到的数据必须一致

代价

  • 需要保证所有节点同步,性能较低
  • 需要加锁或分布式锁,吞吐量受限

2.2.2 A - Availability(可用性)

定义:保证每个请求不管成功或者失败都有响应。

通俗理解

  • 系统始终可用,不会一直等待或无响应
  • 即使部分节点宕机,系统仍能提供服务

示例

用户A请求查询数据:
节点1(北京): 正常响应 ✅
节点2(上海): 宕机了 ❌

系统仍能提供服务,不返回错误,不超时

代价

  • 可能返回旧数据(牺牲一致性)
  • 不能保证所有节点数据同步

2.2.3 P - Partition Tolerance(分区容错性)

定义:系统中任意信息的丢失或失败不会影响系统的继续运作。

通俗理解

  • 在分布式系统中,网络分区是必然发生
  • 系统必须能够在网络分区的情况下继续工作

什么是网络分区

正常情况:
节点1 ←───网络────→ 节点2
↑ ↓
└──────网络────────┘

网络分区:
节点1 XXXXXXXXXX 节点2
X 断网了 X
X X

为什么P是必须的? 在分布式系统中,节点之间通过网络通信,而网络是不可靠的:

  • 光缆被挖断
  • 交换机故障
  • 网络拥塞

因此,分区容错性是分布式系统的天然属性,我们必须接受它。

💡 面试重点:为什么说在分布式系统中P是必须的?

  • 因为网络分区是必然会发生的客观事实
  • 如果放弃P,系统就退化成了单机系统
  • 所以实际上我们只能在"CP"和"AP"之间选择

2.3 CAP的三种组合

2.3.1 CA - 一致性 + 可用性(放弃分区容错)

特点

  • 数据强一致
  • 系统始终可用
  • 不允许网络分区

问题:这在分布式系统中几乎不可能实现!

如果不支持分区容错:
节点1 ←───网络────→ 节点2

一旦网络断开,系统就会:
- 要么拒绝服务(牺牲可用性)
- 要么允许不一致(牺牲一致性)

应用场景

  • 单机系统(RDBMS单实例)
  • 单节点集群(不符合分布式系统定义)

例子

传统单机MySQL:
- 数据强一致(ACID)
- 系统可用(只要数据库不挂)
- 但无法扩展到多节点(因为要保证CA)

2.3.2 CP - 一致性 + 分区容错(放弃可用性)

特点

  • 数据强一致
  • 允许网络分区
  • 在网络分区时,可能会牺牲可用性

行为

网络分区发生时:
节点1 ←───XXXXXXX────→ 节点2

系统选择:
- 节点2拒绝服务(因为无法保证数据一致)
- 等待网络恢复
- 保证数据一致性优先

应用场景

  • 金融系统(银行转账、支付)
  • 库存系统(超卖是绝对不允许的)
  • 配置中心(必须保证配置一致)

Spring Cloud中的实现

  • Consul(默认CP模式)
  • Zookeeper(CP模式)
  • Eureka(AP模式,所以是AP)

代码示例

// CP模式:宁可等待,不可出错
@Service
public class OrderService {

@Transactional
public void createOrder(Order order) {
// 1. 创建订单(本地事务)
orderRepository.save(order);

// 2. 调用库存服务(同步调用,必须成功)
try {
inventoryClient.deductStock(order.getProductId(), 1);
} catch (Exception e) {
// 库存扣减失败,订单创建失败
throw new OrderException("库存不足", e);
}

// 3. 调用支付服务(同步调用,必须成功)
try {
paymentClient.pay(order);
} catch (Exception e) {
// 支付失败,回滚订单
throw new OrderException("支付失败", e);
}
}
}

优缺点

  • ✅ 优点:数据强一致,不会出现脏数据
  • ❌ 缺点:性能较差,可能阻塞等待,系统可用性降低

2.3.3 AP - 可用性 + 分区容错(放弃强一致性)

特点

  • 系统始终可用
  • 允许网络分区
  • 在网络分区时,可能会牺牲一致性

行为

网络分区发生时:
节点1 ←───XXXXXXX────→ 节点2

系统选择:
- 节点2继续提供服务(即使数据可能不一致)
- 返回旧数据(软状态)
- 网络恢复后再同步数据

应用场景

  • 社交网络(点赞、评论)
  • 电商网站(商品浏览)
  • 内容分发系统(CDN)
  • 缓存系统(Redis)

Spring Cloud中的实现

  • Eureka(AP模式,优先保证可用性)
  • Cassandra(AP模式)
  • DynamoDB(AP模式)

代码示例

// AP模式:宁可返回旧数据,不可拒绝服务
@Service
public class ProductService {

@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Autowired
private ProductRepository productRepository;

public Product getProduct(Long productId) {
// 1. 先查缓存(可能不是最新数据)
Product product = redisTemplate.opsForValue()
.get("product:" + productId);

if (product != null) {
// 即使缓存是旧数据,也立即返回
return product;
}

// 2. 缓存未命中,查数据库
product = productRepository.findById(productId);

// 3. 写入缓存
redisTemplate.opsForValue()
.set("product:" + productId, product, 1, TimeUnit.HOURS);

return product;
}
}

优缺点

  • ✅ 优点:高性能,高可用,用户体验好
  • ❌ 缺点:可能读到旧数据,数据最终一致(不是立即一致)

2.4 CAP定理的真相

2.4.1 理论与现实

理论上的CAP

只能三选二:
- CA: 一致性 + 可用性(放弃P)
- CP: 一致性 + 分区容错(放弃A)
- AP: 可用性 + 分区容错(放弃C)

实际中的CAP

由于P是必须的,实际上只有两种选择:
- CP: 牺牲可用性,保证一致性
- AP: 牺牲强一致性,保证可用性

2.4.2 动态切换

在实际系统中,CAP不是一成不变的,可以根据场景动态调整:

@Service
public class SmartService {

@Autowired
private ConfigService configService;

public void processData(Data data) {
// 根据配置动态选择策略
if (configService.isConsistencyPriority()) {
// CP模式:保证一致性
processWithConsistency(data);
} else {
// AP模式:保证可用性
processWithAvailability(data);
}
}
}

💡 面试重点:CAP定理的实际应用

  • 理论上:只能三选二(CA、CP、AP)
  • 实际上:由于P必须保留,只能在CP和AP之间选择
  • 实战中:大部分系统采用"最终一致性"(AP + 补偿机制)

三、BASE理论详解

3.1 什么是BASE理论?

BASE理论是对CAP理论的延伸和补充,核心思想是: 即使无法做到强一致性(CAP中的C),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

BASE的全称

B - Basically Available(基本可用)
A - Soft state(软状态)
S - Eventually consistent(最终一致性)

与CAP的关系

CAP定理说:你无法同时满足C、A、P

BASE理论说:没关系,虽然你无法满足强一致性(C)
但你可以做到"最终一致性",
这样系统仍然是可以用的!

3.2 Basically Available(基本可用)

定义:指分布式系统在出现不可预知故障的时候,允许损失部分可用性,但核心功能仍然可用

注意:不是完全不可用,而是"基本可用"。

3.2.1 基本可用的实现方式

方式一:降级服务

当系统压力大或出现故障时,关闭非核心功能。

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;
@Autowired
private CommentService commentService;
@Autowired
private RecommendationService recommendationService;

public ProductDetail getProductDetail(Long productId) {
// 核心功能:查询商品信息(必须保证)
Product product = productRepository.findById(productId);

ProductDetail detail = new ProductDetail();
detail.setProduct(product);

// 非核心功能1:用户评论(可以降级)
try {
detail.setComments(commentService.getComments(productId));
} catch (Exception e) {
log.warn("评论服务不可用,降级处理", e);
detail.setComments(Collections.emptyList());
}

// 非核心功能2:推荐商品(可以降级)
try {
detail.setRecommendations(
recommendationService.getRecommendations(productId)
);
} catch (Exception e) {
log.warn("推荐服务不可用,降级处理", e);
detail.setRecommendations(Collections.emptyList());
}

return detail;
}
}

方式二:限流

当流量过大时,限制部分请求,保证核心功能可用。

@Component
public class RateLimiter {

private final RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000个请求

public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
}

@Service
public class OrderService {

@Autowired
private RateLimiter rateLimiter;

public void createOrder(Order order) {
// 尝试获取许可
if (!rateLimiter.tryAcquire()) {
// 流量过大,拒绝请求
throw new ServiceUnavailableException("系统繁忙,请稍后再试");
}

// 处理订单
doCreateOrder(order);
}
}

方式三:延迟响应

当系统压力大时,可以适当延长响应时间,但仍要保证响应。

@Service
public class SearchService {

@Autowired
private ElasticsearchTemplate esTemplate;

public SearchResults search(String keyword) {
long startTime = System.currentTimeMillis();

// 执行搜索
SearchResults results = esTemplate.search(keyword);

// 如果搜索时间过长,返回部分结果
long duration = System.currentTimeMillis() - startTime;
if (duration > 3000) {
log.warn("搜索耗时过长: {}ms, 返回部分结果", duration);
results.truncate();
}

return results;
}
}

3.2.2 实际案例

淘宝双11

正常情况:展示商品详情、评论、推荐、优惠券等
双11期间:
- 核心功能:下单、支付(必须保证)✅
- 次要功能:评论、推荐、物流查询(降级或关闭)⚠️
- 奢侈功能:个性化推荐、大数据分析(暂停)❌

结果:系统基本可用,虽然功能减少了,但核心交易不受影响

秒杀系统

正常情况:完整展示商品信息、库存、评价
秒杀期间:
- 核心功能:下单(必须保证)✅
- 次要功能:库存实时查询(静态页面)⚠️
- 奢侈功能:详细评论、推荐(关闭)❌

结果:系统基本可用,虽然信息不准确,但不影响抢购

3.3 Soft State(软状态)

定义:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性

通俗理解

  • 数据可以在不同时间处于不同状态
  • 允许数据是"软"的,不是"硬"的(立即一致)
  • 允许数据在一段时间内是不一致的

3.3.1 软状态的例子

例子1:订单状态

订单的生命周期:
创建 → 待支付 → 已支付 → 待发货 → 已发货 → 已完成

这些状态转换不是瞬间完成的:
"待支付"到"已支付"可能有几分钟延迟
"已发货"到"已收货"可能有几天延迟

允许这种中间状态存在,这就是软状态

代码实现

@Entity
public class Order {
private OrderStatus status; // 订单状态

// 状态转换方法
public void pay() {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("订单状态错误");
}
this.status = OrderStatus.PAID;
}

public void ship() {
if (status != OrderStatus.PAID) {
throw new IllegalStateException("订单未支付");
}
this.status = OrderStatus.SHIPPED;
}
}

例子2:库存同步

用户下单:
订单服务(扣减库存)→ 消息队列 → 库存服务(扣减库存)

中间状态:
- 订单服务:库存已扣减
- 库存服务:还没收到消息,库存未扣减

这段时间内,数据是不一致的,但这是允许的
最终库存服务会同步完成,达到一致

代码实现

@Service
public class OrderService {

@Autowired
private OrderRepository orderRepository;
@Autowired
private StreamBridge streamBridge;

@Transactional
public void createOrder(Order order) {
// 1. 本地扣减库存(本地数据库)
order.setInventoryStatus(InventoryStatus.DEDUCTED);
orderRepository.save(order);

// 2. 发送消息到库存服务(异步)
streamBridge.send("inventory-deduct", order);

// 3. 此时库存服务可能还没处理,数据暂时不一致
// 但这是允许的(软状态)
}
}

3.3.2 软状态 vs 硬状态

硬状态(强一致性)

转账操作:
账户A: 1000元
账户B: 0元

执行转账(A转500给B):
账户A: 500元 ← 必须同时完成
账户B: 500元

任何时刻,要么都是旧值,要么都是新值
不允许中间状态

软状态(最终一致性)

转账操作:
账户A: 1000元
账户B: 0元

执行转账(A转500给B):
T1时刻: 账户A: 500元, 账户B: 0元 ← 中间状态
T2时刻: 账户A: 500元, 账户B: 500元 ← 最终一致

允许T1时刻的中间状态存在

3.4 Eventually Consistent(最终一致性)

定义:系统中的数据最终会达到一致的状态,不需要实时一致

核心思想

不强求"立即一致"
但保证"最终一致"

允许短时间的不一致
但最终所有节点数据会一致

3.4.1 最终一致性的实现方式

方式一:读时修复

在读取数据时,检查并修复不一致的数据。

@Service
public class UserService {

@Autowired
private UserRepository userRepository;
@Autowired
private CacheManager cacheManager;

public User getUser(Long userId) {
// 1. 先查缓存
User cachedUser = cacheManager.get("user:" + userId);

// 2. 查数据库
User dbUser = userRepository.findById(userId);

// 3. 比较数据,如果不一致,修复缓存
if (cachedUser != null && !cachedUser.equals(dbUser)) {
log.warn("缓存数据不一致,修复: userId={}", userId);
cacheManager.put("user:" + userId, dbUser);
}

return dbUser;
}
}

方式二:写时修复

在写入数据时,异步同步到其他节点。

@Service
public class UserService {

@Autowired
private UserRepository userRepository;
@Autowired
private StreamBridge streamBridge;

@Transactional
public void updateUser(User user) {
// 1. 更新主数据库
userRepository.save(user);

// 2. 发送消息,异步更新缓存和其他节点
streamBridge.send("user-update", user);

// 3. 此时缓存可能还是旧数据,但最终会一致
}
}

// 消费者:异步更新缓存
@KafkaListener(topics = "user-update")
public void handleUserUpdate(User user) {
cacheManager.put("user:" + user.getId(), user);
}

方式三:定期修复

后台任务定期检查并修复不一致的数据。

@Component
public class DataConsistencyChecker {

@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryRepository inventoryRepository;

// 每天凌晨执行
@Scheduled(cron = "0 0 2 * * ?")
public void checkAndFix() {
log.info("开始数据一致性检查");

// 查询所有订单
List<Order> orders = orderRepository.findAll();

for (Order order : orders) {
// 检查库存数据是否一致
Inventory inventory = inventoryRepository
.findByProductId(order.getProductId());

if (inventory.getStock() != order.getInventoryStock()) {
log.warn("数据不一致,修复: orderId={}", order.getId());
// 修复数据
inventory.setStock(order.getInventoryStock());
inventoryRepository.save(inventory);
}
}
}
}

3.4.2 最终一致性的典型应用

应用1:DNS解析

你修改了域名的DNS配置:
T0时刻: 修改DNS服务器
T1时刻: 部分用户访问到新IP(部分一致)
T2时刻: 大部分用户访问到新IP(大部分一致)
T3时刻: 所有用户访问到新IP(最终一致)

这个过程可能需要几分钟到几小时

应用2:CDN缓存

你更新了网站图片:
T0时刻: 更新源服务器图片
T1时刻: 部分CDN节点更新(部分一致)
T2时刻: 大部分CDN节点更新(大部分一致)
T3时刻: 所有CDN节点更新(最终一致)

这个过程可能需要几秒到几分钟

应用3:电商库存

用户下单扣减库存:
T0时刻: 订单服务扣减本地库存
T1时刻: 发送消息到库存服务
T2时刻: 库存服务扣减库存
T3时刻: 搜索服务同步库存

T0-T2期间,数据可能不一致
但T2时刻后,数据最终一致

💡 面试重点:BASE理论的核心思想是什么?

  • 基本可用:故障时允许损失部分功能,但核心功能可用
  • 软状态:允许数据存在中间状态
  • 最终一致性:不需要立即一致,但最终会一致
  • 与CAP的关系:BASE是对CAP中"AP"方案的补充,指导如何在AP模式下实现可用系统

四、CAP与BASE的选择

4.1 CP vs AP:如何选择?

4.1.1 选择CP的场景

特征

  • 数据一致性至关重要
  • 宁可暂停服务,也不能接受错误数据
  • 对性能要求不是很高

典型场景

1. 金融系统

@Service
public class BankTransferService {

@Transactional
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
// 1. 扣款(必须成功)
Account from = accountRepository.findByAccountNumber(fromAccount);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
from.setBalance(from.getBalance().subtract(amount));
accountRepository.save(from);

// 2. 加款(必须成功)
Account to = accountRepository.findByAccountNumber(toAccount);
to.setBalance(to.getBalance().add(amount));
accountRepository.save(to);

// 3. 记录交易日志(必须成功)
TransactionLog log = new TransactionLog(fromAccount, toAccount, amount);
transactionLogRepository.save(log);

// 如果任何一步失败,整个操作回滚
// 保证数据强一致性
}
}

2. 库存扣减

@Service
public class InventoryService {

@Transactional
public void deductStock(Long productId, Integer quantity) {
Inventory inventory = inventoryRepository.findByProductId(productId);

// 使用数据库锁,防止超卖
int updated = inventoryRepository.deductStock(
productId,
quantity,
inventory.getVersion() // 乐观锁
);

if (updated == 0) {
throw new OptimisticLockingFailureException("库存扣减失败,请重试");
}
}
}

3. 配置中心

# Consul配置中心
consul:
config:
enabled: true
format: YAML
datacenters: dc1,dc2
# 使用CP模式,保证所有节点配置一致

4.1.2 选择AP的场景

特征

  • 高可用性至关重要
  • 可以容忍短时间的数据不一致
  • 对性能要求较高

典型场景

1. 社交网络

@Service
public class PostService {

@Autowired
private PostRepository postRepository;
@Autowired
private StreamBridge streamBridge;

public void createPost(Post post) {
// 1. 保存帖子到主数据库
postRepository.save(post);

// 2. 异步发送到粉丝的Timeline
streamBridge.send("timeline-update", post);

// 3. 异步更新搜索索引
streamBridge.send("search-index", post);

// 4. 异步更新推荐系统
streamBridge.send("recommendation-update", post);

// 立即返回,不等待异步操作完成
// 用户可能暂时看不到新帖子,但最终会看到
}
}

2. 电商商品浏览

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;
@Autowired
private RedisTemplate<String, Product> redisTemplate;

public Product getProduct(Long productId) {
// 1. 先查缓存(可能不是最新数据)
Product product = redisTemplate.opsForValue()
.get("product:" + productId);

if (product != null) {
return product; // 立即返回,即使可能是旧数据
}

// 2. 查询数据库
product = productRepository.findById(productId);

// 3. 写入缓存
redisTemplate.opsForValue()
.set("product:" + productId, product, 1, TimeUnit.HOURS);

return product;
}

public void updateProduct(Product product) {
// 1. 更新数据库
productRepository.save(product);

// 2. 删除缓存(懒加载策略)
redisTemplate.delete("product:" + product.getId());

// 下次查询时会从数据库加载最新数据
// 允许短暂的数据不一致
}
}

3. 服务注册中心

# Eureka配置(AP模式)
eureka:
server:
# 自我保护模式:即使部分节点宕机,仍然保留注册信息
enable-self-preservation: true
instance:
# 心跳间隔:频繁发送心跳,保证可用性
lease-renewal-interval-in-seconds: 30

4.2 混合模式:根据业务特点灵活选择

在实际系统中,不同的业务可以采用不同的策略

4.2.1 按业务功能区分

电商系统:
├── 用户下单(CP) - 必须保证库存、支付一致性
├── 商品浏览(AP) - 可以容忍短暂的数据延迟
├── 用户评论(AP) - 评论可以稍后同步
├── 搜索(AP) - 搜索结果可以延迟更新
└── 支付(CP) - 必须保证资金安全

代码示例

@Service
public class OrderService {

@Autowired
private InventoryService inventoryService; // CP模式
@Autowired
private ProductService productService; // AP模式
@Autowired
private PaymentService paymentService; // CP模式

@Transactional
public void createOrder(Order order) {
// 1. 扣减库存(CP:同步调用,必须成功)
inventoryService.deductStock(order.getProductId(), 1);

// 2. 创建订单(CP:本地事务)
orderRepository.save(order);

// 3. 调用支付(CP:同步调用,必须成功)
paymentService.pay(order);

// 4. 更新商品浏览次数(AP:异步调用)
// 使用消息队列,允许延迟
streamBridge.send("product-view", order.getProductId());
}
}

4.2.2 按数据类型区分

数据分类:
├── 核心业务数据(CP)
│ ├── 订单数据
│ ├── 支付数据
│ └── 库存数据

└── 非核心数据(AP)
├── 用户行为数据(浏览、点击)
├── 日志数据
└── 统计数据

配置示例

spring:
datasource:
# 核心业务数据:使用MySQL(支持ACID,强一致)
core:
url: jdbc:mysql://mysql-core:3306/core_db
# 统计数据:使用MongoDB(最终一致)
analytics:
url: mongodb://mongo-analytics:27017/analytics_db

data:
redis:
# 缓存:使用Redis(AP,最终一致)
host: redis-cache
port: 6379

4.3 Spring Cloud中的CAP应用

4.3.1 Eureka(AP模式)

特点

  • 优先保证可用性
  • 即使部分节点宕机,仍然提供服务
  • 可能返回过期的服务实例信息

配置

eureka:
server:
# 自我保护模式:AP模式的核心
# 当网络分区发生时,保留注册信息
enable-self-preservation: true
# 期望心跳比例:低于这个值时进入自我保护
renewal-percent-threshold: 0.85

instance:
# 心跳间隔:30秒
lease-renewal-interval-in-seconds: 30
# 心跳超时:90秒
lease-expiration-duration-in-seconds: 90

行为

正常情况:
服务实例A 每30秒发送心跳 → Eureka Server

网络分区发生:
服务实例A XXXXXXX Eureka Server

Eureka的选择:
- 不立即删除服务A的注册信息(AP模式)
- 保留注册信息(可能过期)
- 等待网络恢复

代码示例

@EnableEurekaClient
@SpringBootApplication
public class OrderServiceApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

// 使用Eureka发现服务
@Service
public class RemoteServiceClient {

@Autowired
private DiscoveryClient discoveryClient;

public String callRemoteService() {
// 从Eureka获取服务实例列表(可能包含过期实例)
List<ServiceInstance> instances = discoveryClient
.getInstances("remote-service");

// 调用服务
for (ServiceInstance instance : instances) {
try {
return restTemplate.getForObject(
instance.getUri() + "/api/data",
String.class
);
} catch (Exception e) {
// 调用失败,尝试下一个实例
log.warn("调用失败: {}", instance.getUri());
}
}

throw new ServiceUnavailableException("所有实例都不可用");
}
}

4.3.2 Consul(CP模式)

特点

  • 优先保证一致性
  • 使用Raft协议保证数据一致
  • Leader宕机时会重新选举

配置

spring:
cloud:
consul:
host: localhost
port: 8500
discovery:
# 注册服务
register: true
# 健康检查
health-check-interval: 10s
health-check-critical-timeout: 30s
# 使用CP模式
prefer-ip-address: true

行为

正常情况:
服务实例A → Consul Leader → 数据同步 → Consul Follower

网络分区发生:
服务实例A XXXXXXX Consul Leader

Consul的选择:
- 如果Leader失去联系,重新选举Leader(CP模式)
- 选举期间,服务可能不可用
- 保证数据一致性

代码示例

@EnableDiscoveryClient
@SpringBootApplication
public class OrderServiceApplication {

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}

// 使用Consul发现服务
@Service
public class RemoteServiceClient {

@Autowired
private DiscoveryClient discoveryClient;

public String callRemoteService() {
// 从Consul获取服务实例列表(保证一致)
List<ServiceInstance> instances = discoveryClient
.getInstances("remote-service");

if (instances.isEmpty()) {
// 如果没有可用实例,抛出异常(不会返回过期实例)
throw new ServiceUnavailableException("没有可用的服务实例");
}

// 调用服务
return restTemplate.getForObject(
instances.get(0).getUri() + "/api/data",
String.class
);
}
}

4.3.3 Nacos(支持AP和CP切换)

特点

  • 可以根据业务需要切换AP或CP模式
  • 临时实例:AP模式
  • 持久化实例:CP模式

配置

spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
# 临时实例(AP模式)
ephemeral: true
# 命名空间
namespace: public

切换模式

// 通过Nacos API切换模式
@Configuration
public class NacosConfig {

@Bean
public NamingService namingService() throws NacosException {
Properties properties = new Properties();
properties.put("serverAddr", "localhost:8848");

// AP模式:临时实例
properties.put("ephemeral", "true");

// CP模式:持久化实例
// properties.put("ephemeral", "false");

return NamingFactory.createNamingService(properties);
}
}

💡 面试重点:Eureka、Consul、Nacos的区别?

  • Eureka:AP模式,保证可用性,可能返回过期实例
  • Consul:CP模式,保证一致性,使用Raft协议
  • Nacos:支持AP和CP切换,灵活适配不同业务

五、实战案例:秒杀系统的CAP应用

5.1 场景分析

秒杀系统的特点

  • 流量巨大(平时100 QPS,秒杀时100000 QPS)
  • 库存有限(100个商品,100万人抢)
  • 不能超卖(库存必须准确)

CAP的选择

核心功能(库存扣减):CP模式
- 绝对不能超卖
- 宁可拒绝服务,也不能卖多了

非核心功能(商品展示):AP模式
- 可以显示缓存数据
- 允许短暂延迟

5.2 架构设计

用户请求

CDN(缓存静态资源)- AP模式

API网关(限流)- AP模式

秒杀服务(订单创建)- CP模式

消息队列(异步处理)- 最终一致性

├── 库存服务(扣减库存)- CP模式
├── 支付服务(处理支付)- CP模式
└── 通知服务(发送通知)- AP模式

5.3 代码实现

5.3.1 商品查询(AP模式)

@Service
public class ProductQueryService {

@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Autowired
private ProductRepository productRepository;

/**
* 查询商品信息(AP模式)
* 优先使用缓存,允许短暂的数据延迟
*/
public Product getProduct(Long productId) {
// 1. 查询缓存
Product product = redisTemplate.opsForValue()
.get("product:" + productId);

if (product != null) {
// 命中缓存,立即返回(可能是旧数据)
return product;
}

// 2. 缓存未命中,查询数据库
product = productRepository.findById(productId);

// 3. 写入缓存
redisTemplate.opsForValue()
.set("product:" + productId, product, 1, TimeUnit.HOURS);

return product;
}
}

5.3.2 库存扣减(CP模式)

@Service
public class InventoryService {

@Autowired
private InventoryRepository inventoryRepository;

/**
* 扣减库存(CP模式)
* 必须保证数据强一致性,不能超卖
*/
@Transactional
public boolean deductStock(Long productId, Integer quantity) {
// 1. 使用乐观锁扣减库存
int updated = inventoryRepository.deductStock(
productId,
quantity,
new Date() // 版本号
);

// 2. 如果更新失败,说明库存不足或并发冲突
if (updated == 0) {
throw new InsufficientStockException("库存不足");
}

return true;
}
}

// Repository层
@Repository
public interface InventoryRepository extends JpaRepository<Inventory, Long> {

@Transactional
@Modifying
@Query("UPDATE Inventory i SET i.stock = i.stock - :quantity, " +
"i.updateTime = :version " +
"WHERE i.productId = :productId " +
"AND i.stock >= :quantity " +
"AND i.updateTime < :version")
int deductStock(
@Param("productId") Long productId,
@Param("quantity") Integer quantity,
@Param("version") Date version
);
}

5.3.3 订单创建(混合模式)

@Service
public class SeckillOrderService {

@Autowired
private InventoryService inventoryService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private StreamBridge streamBridge;

/**
* 创建秒杀订单
* 核心流程:CP模式
* 非核心流程:AP模式
*/
@Transactional
public SeckillOrder createOrder(SeckillRequest request) {
// 1. 校验用户是否重复购买(CP模式)
if (orderRepository.existsByUserIdAndActivityId(
request.getUserId(),
request.getActivityId()
)) {
throw new DuplicatePurchaseException("重复购买");
}

// 2. 扣减库存(CP模式:同步调用)
boolean success = inventoryService.deductStock(
request.getProductId(),
1
);

// 3. 创建订单(CP模式:本地事务)
SeckillOrder order = SeckillOrder.builder()
.orderId(generateOrderId())
.userId(request.getUserId())
.productId(request.getProductId())
.status(OrderStatus.CREATED)
.createTime(LocalDateTime.now())
.build();

orderRepository.save(order);

// 4. 发送消息到MQ(异步处理,最终一致性)
SeckillEvent event = SeckillEvent.builder()
.orderId(order.getOrderId())
.userId(order.getUserId())
.productId(order.getProductId())
.build();

streamBridge.send("seckill-order", event);

// 5. 立即返回,不等待异步操作完成
return order;
}
}

5.3.4 消息消费(BASE理论)

@Service
public class SeckillOrderConsumer {

@Autowired
private PaymentService paymentService;
@Autowired
private NotificationService notificationService;
@Autowired
private DataAnalyticsService analyticsService;

/**
* 处理秒杀订单(异步)
* 实现BASE理论的最终一致性
*/
@StreamListener(target = Sink.INPUT)
public void handleSeckillOrder(SeckillEvent event) {
String orderId = event.getOrderId();

try {
// 1. 调用支付(核心功能,CP模式)
// 如果失败,重试3次
paymentService.pay(orderId);

// 2. 发送短信通知(非核心功能,AP模式)
// 如果失败,记录日志,不影响主流程
try {
notificationService.sendSms(
event.getUserId(),
"秒杀成功!订单号:" + orderId
);
} catch (Exception e) {
log.error("发送短信失败: orderId={}", orderId, e);
}

// 3. 更新大数据统计(非核心功能,AP模式)
// 如果失败,定期补偿
try {
analyticsService.recordSeckill(event);
} catch (Exception e) {
log.error("记录统计数据失败: orderId={}", orderId, e);
// 存储到补偿队列,定期重试
compensationQueue.add(event);
}

log.info("秒杀订单处理完成: orderId={}", orderId);

} catch (Exception e) {
log.error("处理秒杀订单失败: orderId={}", orderId, e);
throw e; // 抛出异常,触发重试
}
}
}

5.4 性能优化

5.4.1 缓存优化(AP模式)

@Service
public class ProductCacheService {

@Autowired
private RedisTemplate<String, Product> redisTemplate;
@Autowired
private ProductRepository productRepository;

/**
* 预热缓存
* 秒杀活动开始前,将商品信息加载到缓存
*/
@PostConstruct
public void warmUpCache() {
List<Product> products = productRepository.findAll();

products.forEach(product -> {
redisTemplate.opsForValue()
.set("product:" + product.getId(), product, 1, TimeUnit.HOURS);
});

log.info("缓存预热完成,加载{}个商品", products.size());
}

/**
* 查询商品(AP模式:优先使用缓存)
*/
public Product getProduct(Long productId) {
// 先查缓存
Product product = redisTemplate.opsForValue()
.get("product:" + productId);

if (product != null) {
return product;
}

// 缓存未命中,查询数据库
product = productRepository.findById(productId);

// 写入缓存
if (product != null) {
redisTemplate.opsForValue()
.set("product:" + productId, product, 1, TimeUnit.HOURS);
}

return product;
}
}

5.4.2 限流降级(基本可用)

@Component
public class SeckillRateLimiter {

// 使用Guava RateLimiter限流
private final RateLimiter rateLimiter = RateLimiter.create(10000); // 每秒10000个请求

/**
* 尝试获取许可
* 如果超过限流阈值,返回false
*/
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
}

@Service
public class SeckillService {

@Autowired
private SeckillRateLimiter rateLimiter;

public SeckillOrder seckill(SeckillRequest request) {
// 1. 限流检查
if (!rateLimiter.tryAcquire()) {
throw new RateLimitExceededException("系统繁忙,请稍后再试");
}

// 2. 处理秒杀逻辑
return doSeckill(request);
}
}

六、常见面试题

6.1 基础概念题

Q1:什么是CAP定理?

A:CAP定理是分布式系统设计的基础理论,指出一个分布式系统最多只能同时满足以下三个属性中的两个

  1. C(Consistency)一致性

    • 所有节点在同一时刻看到的数据相同
    • 就像多个副本必须实时同步
  2. A(Availability)可用性

    • 每个请求都能得到响应(成功或失败)
    • 系统始终可用,不会一直等待
  3. P(Partition Tolerance)分区容错性

    • 系统在网络分区时仍能继续工作
    • 分布式系统的天然属性

关键点

  • 理论上:只能三选二(CA、CP、AP)
  • 实际上:由于P必须保留,只能在CP和AP之间选择

Q2:什么是BASE理论?

A:BASE理论是对CAP理论的延伸和补充,核心思想是:即使无法做到强一致性(CAP中的C),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

BASE的三个核心概念

  1. B(Basically Available)基本可用

    • 故障时允许损失部分功能
    • 但核心功能仍然可用
    • 例如:降级、限流
  2. S(Soft State)软状态

    • 允许数据存在中间状态
    • 数据可以在不同时间处于不同状态
    • 例如:订单状态"待支付"→"已支付"→"已发货"
  3. E(Eventually Consistent)最终一致性

    • 不需要立即一致
    • 但最终会一致
    • 例如:DNS解析、CDN缓存

与CAP的关系

  • BASE是对CAP中"AP"方案的补充
  • 指导如何在AP模式下实现可用系统

Q3:CAP和BASE的关系是什么?

A

对比项CAP定理BASE理论
性质理论限制实践指导
作用告诉我们限制告诉我们如何权衡
内容C、A、P三选二基本可用、软状态、最终一致
适用场景分布式系统设计分布式系统实现

关系

  1. CAP是理论基础,BASE是实践方法
  2. CAP告诉我们"做不到"同时满足三个属性
  3. BASE告诉我们"如何做到"在CAP限制下设计系统
  4. BASE是对CAP中"AP"方案的延伸和补充

Q4:为什么说在分布式系统中P是必须的?

A:因为网络分区是必然会发生的客观事实

具体原因

  1. 分布式系统的定义

    • 多个节点通过网络连接
    • 节点之间需要通信
    • 通信依赖网络
  2. 网络不可靠

    • 硬件故障(光缆被挖断、交换机故障)
    • 软件故障(网络拥塞、DNS解析失败)
    • 自然灾害(地震、洪水)
  3. 放弃P的后果

    • 系统退化成单机系统
    • 不符合分布式系统的定义
    • 失去分布式系统的优势

结论

  • 因此,P是必须的
  • 实际上我们只能在CP和AP之间选择
  • 不是"三选二",而是"二选一"

6.2 实战应用题

Q5:在什么情况下选择CP?

A:选择CP的场景通常是数据一致性至关重要的场景。

典型场景

  1. 金融系统

    • 银行转账:资金必须准确
    • 股票交易:交易数据不能错
    • 支付系统:金额必须精确
  2. 库存系统

    • 电商库存:不能超卖
    • 票务系统:不能超售
  3. 配置中心

    • 系统配置:所有节点必须一致
    • 服务发现:保证服务列表准确

代码示例

@Service
public class BankTransferService {

@Transactional
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
// 1. 扣款(必须成功)
Account from = accountRepository.findByAccountNumber(fromAccount);
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException("余额不足");
}
from.setBalance(from.getBalance().subtract(amount));
accountRepository.save(from);

// 2. 加款(必须成功)
Account to = accountRepository.findByAccountNumber(toAccount);
to.setBalance(to.getBalance().add(amount));
accountRepository.save(to);

// 任何一步失败,整个操作回滚
// 保证数据强一致性
}
}

优缺点

  • ✅ 优点:数据准确,不会出现脏数据
  • ❌ 缺点:性能较差,可能阻塞等待

Q6:在什么情况下选择AP?

A:选择AP的场景通常是高可用性至关重要的场景。

典型场景

  1. 社交网络

    • 用户发帖:可以稍后同步
    • 点赞评论:允许短暂延迟
  2. 内容分发

    • CDN缓存:允许延迟更新
    • 视频分发:允许延迟
  3. 搜索引擎

    • 索引更新:可以异步
    • 搜索结果:允许短暂延迟

代码示例

@Service
public class PostService {

@Autowired
private PostRepository postRepository;
@Autowired
private StreamBridge streamBridge;

public void createPost(Post post) {
// 1. 保存帖子到主数据库
postRepository.save(post);

// 2. 异步发送到粉丝的Timeline
streamBridge.send("timeline-update", post);

// 3. 异步更新搜索索引
streamBridge.send("search-index", post);

// 立即返回,不等待异步操作完成
// 用户可能暂时看不到新帖子,但最终会看到
}
}

优缺点

  • ✅ 优点:高性能,高可用
  • ❌ 缺点:可能读到旧数据

Q7:如何实现最终一致性?

A:最终一致性可以通过多种方式实现。

方式一:读时修复

在读取数据时,检查并修复不一致的数据。

@Service
public class UserService {

@Autowired
private UserRepository userRepository;
@Autowired
private CacheManager cacheManager;

public User getUser(Long userId) {
// 1. 查缓存
User cachedUser = cacheManager.get("user:" + userId);

// 2. 查数据库
User dbUser = userRepository.findById(userId);

// 3. 比较数据,如果不一致,修复缓存
if (cachedUser != null && !cachedUser.equals(dbUser)) {
cacheManager.put("user:" + userId, dbUser);
}

return dbUser;
}
}

方式二:写时修复

在写入数据时,异步同步到其他节点。

@Service
public class UserService {

@Transactional
public void updateUser(User user) {
// 1. 更新主数据库
userRepository.save(user);

// 2. 发送消息,异步更新缓存
streamBridge.send("user-update", user);
}
}

方式三:定期修复

后台任务定期检查并修复不一致的数据。

@Component
public class DataConsistencyChecker {

// 每天凌晨执行
@Scheduled(cron = "0 0 2 * * ?")
public void checkAndFix() {
List<Order> orders = orderRepository.findAll();

for (Order order : orders) {
Inventory inventory = inventoryRepository
.findByProductId(order.getProductId());

if (inventory.getStock() != order.getInventoryStock()) {
// 修复数据
inventory.setStock(order.getInventoryStock());
inventoryRepository.save(inventory);
}
}
}
}

Q8:Eureka和Consul的区别是什么?

A

对比项EurekaConsul
CAP模式AP(可用性优先)CP(一致性优先)
协议自定义协议Raft协议
一致性弱一致性(可能返回过期实例)强一致性
可用性高(自我保护模式)中(Leader选举期间不可用)
健康检查Client端心跳Server端主动检查
使用场景大规模服务注册配置中心、小规模服务注册

Eureka(AP模式)

eureka:
server:
# 自我保护模式:AP模式的核心
enable-self-preservation: true
instance:
# 频繁发送心跳,保证可用性
lease-renewal-interval-in-seconds: 30

Consul(CP模式)

spring:
cloud:
consul:
discovery:
# 使用Raft协议保证一致性
prefer-ip-address: true

选择建议

  • 大规模微服务:选择Eureka(AP)
  • 需要强一致性的配置中心:选择Consul(CP)
  • 需要灵活切换:选择Nacos(支持AP和CP)

Q9:如何设计一个既保证一致性又保证可用性的系统?

A:这是一个经典的两难选择,但我们可以通过混合模式补偿机制来平衡。

方案一:核心功能CP,非核心功能AP

@Service
public class OrderService {

@Autowired
private InventoryService inventoryService; // CP模式
@Autowired
private CommentService commentService; // AP模式

@Transactional
public void createOrder(Order order) {
// 1. 扣减库存(CP:同步调用,必须成功)
inventoryService.deductStock(order.getProductId(), 1);

// 2. 创建订单(CP:本地事务)
orderRepository.save(order);

// 3. 发送评论(AP:异步调用,允许延迟)
streamBridge.send("order-comment", order);
}
}

方案二:使用Saga模式补偿

@Service
public class OrderSagaService {

public void executeOrder(Order order) {
try {
// 步骤1:创建订单
createOrder(order);

// 步骤2:扣减库存
deductInventory(order);

// 步骤3:处理支付
processPayment(order);

} catch (Exception e) {
// 补偿:回滚操作
compensate(order);
}
}

private void compensate(Order order) {
try {
// 补偿支付
if (order.getPaymentStatus() == PaymentStatus.SUCCESS) {
refundPayment(order);
}

// 补偿库存
if (order.getInventoryStatus() == InventoryStatus.DEDUCTED) {
restoreInventory(order);
}

// 取消订单
cancelOrder(order);

} catch (Exception e) {
log.error("补偿失败", e);
// 人工介入
}
}
}

方案三:使用TCC模式

@Service
public class OrderTccService {

public void executeOrder(Order order) {
// Try阶段:预留资源
inventoryService.tryDeductStock(order.getProductId(), 1);
paymentService.tryPay(order);

try {
// Confirm阶段:确认操作
inventoryService.confirmDeductStock(order.getProductId());
paymentService.confirmPay(order);

} catch (Exception e) {
// Cancel阶段:取消操作
inventoryService.cancelDeductStock(order.getProductId());
paymentService.cancelPay(order);
}
}
}

6.3 高级应用题

Q10:在微服务架构中,如何平衡CAP和BASE?

A:在微服务架构中,我们不是做选择题,而是根据业务特点灵活应用

实践原则

  1. 按业务功能区分
核心业务(CP):
- 订单创建
- 库存扣减
- 支付处理

非核心业务(AP):
- 商品浏览
- 用户评论
- 数据统计
  1. 按数据类型区分
核心数据(CP):
- 使用MySQL、PostgreSQL
- 支持ACID事务

非核心数据(AP):
- 使用MongoDB、Redis
- 最终一致性
  1. 按用户体验区分
实时性要求高(CP):
- 支付确认
- 库存查询

实时性要求低(AP):
- 推荐系统
- 数据分析

架构示例

// 订单服务:混合模式
@Service
public class OrderService {

@Autowired
private InventoryService inventoryService; // CP模式
@Autowired
private ProductService productService; // AP模式
@Autowired
private NotificationService notificationService; // AP模式
@Autowired
private StreamBridge streamBridge;

@Transactional
public void createOrder(Order order) {
// 1. 扣减库存(CP:同步调用)
inventoryService.deductStock(order.getProductId(), 1);

// 2. 创建订单(CP:本地事务)
orderRepository.save(order);

// 3. 支付(CP:同步调用)
paymentService.pay(order);

// 4. 异步发送通知(AP:异步调用)
streamBridge.send("notification", order);

// 5. 异步更新推荐(AP:异步调用)
streamBridge.send("recommendation", order);
}
}

配置示例

# 核心业务:使用MySQL(CP)
spring:
datasource:
core:
url: jdbc:mysql://mysql-core:3306/core_db

# 非核心数据:使用MongoDB(AP)
spring:
data:
mongodb:
uri: mongodb://mongo-analytics:27017/analytics_db

# 缓存:使用Redis(AP)
spring:
redis:
host: redis-cache
port: 6379

七、总结与建议

7.1 核心要点

  1. CAP定理是理论基础

    • 告诉我们分布式系统的限制
    • 实际上只能在CP和AP之间选择
  2. BASE理论是实践指导

    • 告诉我们如何在CAP限制下设计系统
    • 基本可用、软状态、最终一致性
  3. 不要死板地套用理论

    • 根据业务特点灵活选择
    • 核心功能CP,非核心功能AP
    • 混合模式是最佳实践
  4. 最终一致性是主流

    • 大部分互联网系统采用AP + 最终一致性
    • 通过补偿机制、Saga模式、TCC模式保证数据一致

7.2 学习路径

初学者

  1. 理解CAP定理的定义和三个属性
  2. 理解BASE理论的核心思想
  3. 了解CP和AP的区别
  4. 掌握基本的分布式事务概念

进阶开发者

  1. 理解分布式系统的权衡
  2. 掌握最终一致性的实现方式
  3. 了解Saga、TCC等分布式事务模式
  4. 熟悉Spring Cloud中的CAP应用(Eureka、Consul、Nacos)

架构师

  1. 根据业务特点设计合适的CAP策略
  2. 设计混合模式的系统架构
  3. 处理分布式事务的各种边界情况
  4. 建立完善的数据一致性监控和补偿机制

7.3 参考资源

经典论文

  • CAP定理原文:Brewer, E. A. (2000). "Towards robust distributed systems"
  • BASE理论:Pritchett, D. (2008). "Base: An Acid Alternative"

推荐阅读

  • 《分布式系统原理与范型》
  • 《数据密集型应用系统设计》
  • 《微服务架构设计模式》
  • 《从Paxos到Zookeeper》

实战项目

  • Spring Cloud Eureka(AP模式)
  • Consul(CP模式)
  • Nacos(支持AP和CP切换)
  • Seata(分布式事务解决方案)

💡 最后建议:CAP和BASE是分布式系统设计的理论基础,但不要被理论束缚。

  • 理论指导实践:了解限制,才能更好地设计系统
  • 实践验证理论:根据实际业务选择合适的策略
  • 没有银弹:没有一种方案适合所有场景
  • 权衡是关键:理解业务需求,权衡一致性和可用性
  • 最终一致性是趋势:大多数互联网系统采用AP + 最终一致性的方案