# CAP 理论和 BASE 理论 ## 问题 1. 什么是 CAP 理论?CAP 三者为什么不可兼得? 2. 什么是 BASE 理论? 3. CP、AP、AP 架构分别适用于什么场景? 4. Zookeeper、Eureka、Nacos、Consul 分别是 CP 还是 AP? 5. 在实际项目中如何权衡一致性、可用性、分区容错性? --- ## 标准答案 ### 1. CAP 理论 #### **定义** CAP 是指分布式系统中的三个核心指标: | 指标 | 说明 | 示例 | |------|------|------| | **Consistency(一致性)** | 所有节点在同一时间看到相同的数据 | 写入后,所有节点立即读取到新数据 | | **Availability(可用性)** | 系统持续提供服务,每个请求都能得到响应 | 即使部分节点故障,系统仍能响应 | | **Partition Tolerance(分区容错性)** | 系统在网络分区时仍能继续运行 | 节点间网络断开,系统仍能工作 | --- #### **CAP 定理** **一个分布式系统最多只能同时满足两项**: ``` ┌─────────────────────────────────────┐ │ CAP 三选二 │ │ │ │ CA ────┐ │ │ │ │ │ ╱ │ ╲ │ │ ╱ │ ╲ │ │ ╱ │ ╲ │ │ ●────────●──────● │ │ C P A │ │ │ │ CP AP CA │ └─────────────────────────────────────┘ ``` **为什么?**(证明) ``` 场景:两个节点 N1、N2,网络分区(P) 情况 1:保证 C(一致性) ├─ N1 写入数据 └─ 为了保证一致性,N2 必须拒绝读取(牺牲 A) 情况 2:保证 A(可用性) ├─ N1 写入数据 └─ 为了保证可用性,N2 返回旧数据(牺牲 C) 结论:在有分区(P)的情况下,C 和 A 无法同时满足 ``` --- #### **CP、AP、CA 架构** **1. CP(一致性 + 分区容错)** **特点**: - 保证数据一致性 - 分区时部分节点不可用 **适用场景**: - 金融系统(转账、支付) - 库存系统(超卖不可接受) **代表系统**: - Zookeeper(CP) - HBase(CP) - Redis Cluster(CP,主从切换时短暂不可用) **示例**: ```java // Zookeeper 写入流程 client.setData("/node", data); // 等待大多数节点确认 // 如果网络分区,部分节点无法写入 ``` --- **2. AP(可用性 + 分区容错)** **特点**: - 保证系统可用 - 分区时可能读到脏数据 **适用场景**: - 社交媒体(点赞、评论) - 内容分发(CDN) - 用户行为统计 **代表系统**: - Cassandra(AP) - DynamoDB(AP) - Eureka(AP) - DNS(AP) **示例**: ```java // Cassandra 写入 session.execute("INSERT INTO users (id, name) VALUES (1, 'Alice')"); // 写入成功立即返回 // 数据可能尚未复制到其他节点(最终一致性) ``` --- **3. CA(一致性 + 可用性)** **注意**:**在分布式系统中,CA 不存在**(因为网络分区不可避免)。 **CA 存在于**: - 单机系统(RDBMS) - 传统关系型数据库(MySQL、PostgreSQL) --- ### 2. BASE 理论 #### **定义** BASE 是对 CAP 中 AP 方案的补充,通过**牺牲强一致性**来获得**高可用性**。 | 指标 | 说明 | 示例 | |------|------|------| | **Basically Available(基本可用)** | 系统出现故障时,允许损失部分可用性 | 秒杀时拒绝部分请求 | | **Soft state(软状态)** | 允许数据存在中间状态 | 订单状态:待支付 → 已支付 | | **Eventually consistent(最终一致性)** | 数据最终会达到一致状态 | 支付后 1 秒内到账 | --- #### **BASE vs ACID** | 特性 | ACID(传统数据库) | BASE(NoSQL) | |------|-------------------|---------------| | **一致性** | 强一致性(立即) | 最终一致性(延迟) | | **可用性** | 可能(锁、事务) | 高(无锁、异步) | | **隔离性** | 严格(锁) | 松散 | | **持久性** | 强 | 弱(可能丢失) | --- #### **最终一致性的实现** **1. 读时修复(Read Repair)**: ```java // 读取时检查一致性 public User getUser(Long userId) { // 从多个节点读取 User user1 = node1.get(userId); User user2 = node2.get(userId); // 发现不一致,修复 if (!user1.equals(user2)) { // 使用版本号或时间戳决定哪个更新 User latest = user1.getVersion() > user2.getVersion() ? user1 : user2; // 异步修复旧数据 asyncRepair(latest); return latest; } return user1; } ``` --- **2. 写时修复(Write Repair)**: ```java // 写入时同步到所有节点 public void saveUser(User user) { // 写入主节点 masterNode.save(user); // 异步同步到从节点 for (Node slave : slaves) { CompletableFuture.runAsync(() -> { slave.save(user); }); } } ``` --- **3. 异步复制**: ``` 主节点收到写入 ↓ 立即返回成功 ↓ 异步复制到从节点 ↓ 最终一致 ``` --- ### 3. 主流注册中心对比 #### **Zookeeper(CP)** **特点**: - 保证一致性(ZAB 协议) - Leader 挂了会重新选举(期间不可用) **适用场景**: - 需要强一致性 - 对可用性要求不高(如配置中心) **示例**: ```java // Zookeeper 注册 zk.create("/services/order/192.168.1.10:8080", data, ZooDefs.Ids.OPEN, NodeMode.EPHEMERAL); ``` --- #### **Eureka(AP)** **特点**: - 保证可用性 - 客户端缓存注册信息 - 网络分区时仍能提供服务 **适用场景**: - 对可用性要求高 - 可容忍短暂的服务发现不准确 **示例**: ```java // Eureka 注册 @Bean public EurekaInstanceBeanBean eurekaInstanceBean(InetAddress inetAddress) { EurekaInstanceBeanBean bean = new EurekaInstanceBeanBean(); bean.setHostname(inetAddress.getHostAddress()); bean.setAppName("order-service"); bean.setNonSecurePort(8080); return bean; } ``` --- #### **Nacos(AP + CP)** **特点**: - 支持 AP 和 CP 切换 - 默认 AP(临时实例) - 可配置 CP(持久化实例) **配置**: ```yaml # Nacos 配置 spring: cloud: nacos: discovery: server-addr: localhost:8848 ephemeral: true # true=AP, false=CP ``` --- #### **Consul(CP)** **特点**: - 保证一致性(Raft 协议) - 支持 KV 存储 - 支持健康检查 **适用场景**: - 服务发现 - 配置中心 - 分布式锁 --- ### 4. 实际项目应用 #### **场景 1:订单系统(CP)** **需求**: - 不能超卖 - 库存数据必须准确 **方案**: ``` 1. 使用分布式锁(Redis、Zookeeper) 2. 数据库使用强一致性事务 3. 库存扣减使用串行化 ``` **代码**: ```java @Transactional public void createOrder(Order order) { // 1. 获取分布式锁 RLock lock = redissonClient.getLock("product:" + order.getProductId()); lock.lock(); try { // 2. 查询库存 Product product = productMapper.selectById(order.getProductId()); if (product.getStock() < order.getQuantity()) { throw new BusinessException("库存不足"); } // 3. 扣减库存 product.setStock(product.getStock() - order.getQuantity()); productMapper.updateById(product); // 4. 创建订单 orderMapper.insert(order); } finally { lock.unlock(); } } ``` --- #### **场景 2:社交点赞(AP)** **需求**: - 高并发点赞 - 允许点赞数短暂不准确 **方案**: ``` 1. 先更新缓存(Redis) 2. 异步同步到数据库 3. 最终一致(1 秒内) ``` **代码**: ```java public void like(Long userId, Long postId) { // 1. 更新缓存(立即返回) redisTemplate.opsForSet().add("post:" + postId + ":likes", userId); // 2. 异步更新数据库 CompletableFuture.runAsync(() -> { likeMapper.insert(new Like(userId, postId)); }); } public Long getLikeCount(Long postId) { // 1. 先查缓存 Long count = redisTemplate.opsForSet().size("post:" + postId + ":likes"); // 2. 如果缓存不存在,查数据库 if (count == null || count == 0) { count = likeMapper.countByPostId(postId); redisTemplate.opsForValue().set("post:" + postId + ":count", count, 1, TimeUnit.HOURS); } return count; } ``` --- #### **场景 3:库存同步(最终一致性)** **需求**: - 多个仓库库存同步 - 允许短暂不一致 **方案**: ``` 1. 使用消息队列(RocketMQ) 2. 事务消息保证不丢失 3. 消费者重试保证最终一致 ``` **代码**: ```java // 1. 发送事务消息 @Transactional public void updateStock(Long productId, int quantity) { // 更新数据库 productMapper.updateStock(productId, quantity); // 发送事务消息 Message msg = new Message("StockTopic", "StockUpdate", JSON.toJSONString(new StockUpdateEvent(productId, quantity)).getBytes()); rocketMQTemplate.sendMessageInTransaction(msg, null); } // 2. 消费消息 @RocketMQMessageListener(topic = "StockTopic", consumerGroup = "stock-consumer") public class StockConsumer implements RocketMQListener { @Override public void onMessage(StockUpdateEvent event) { // 同步到其他仓库 warehouseService.syncStock(event.getProductId(), event.getQuantity()); } } ``` --- ### 5. 阿里 P7 加分项 **深度理解**: - 理解 CAP 理论的局限性(如网络延迟、时钟问题) - 理解各种一致性协议(Paxos、Raft、ZAB) - 理解分布式事务的权衡 **实战经验**: - 有设计 CP 或 AP 系统的经验 - 有处理数据不一致问题的经验 - 有最终一致性调优的经验 **架构能力**: - 能根据业务特点选择合适的架构 - 能设计混合架构(部分 CP、部分 AP) - 能设计数据修复和补偿机制 **技术选型**: - 了解各种注册中心和存储系统的 CAP 特性 - 能根据业务特点选择合适的技术 - 有分布式系统设计经验