# 分布式系统入门:CAP、BASE、一致性模型与工程取舍
单机系统变成分布式系统后,最大的变化不是“机器变多了”,而是失败变成常态:网络可能延迟、丢包或分区;节点可能宕机、暂停或重启;时钟也不一定一致。设计分布式系统的核心,是在这些不确定性下仍然给业务一个可解释、可恢复的结果。
## 一、为什么需要分布式
常见原因有四个:
1. 容量超过单机上限:数据量、连接数或计算量必须拆分。
2. 提高可用性:单个节点故障时由其他节点接管。
3. 降低访问延迟:把服务和数据部署到更靠近用户的位置。
4. 组织与业务拆分:不同团队独立发布、扩容和治理。
分布式不是免费的。它会引入远程调用、数据复制、一致性、重试、幂等、故障恢复和可观测性等成本。只要单机或主从架构能满足目标,就不必为了“先进”过早拆分。
## 二、正确理解 CAP
CAP 包含三个属性:
- 一致性(Consistency):一次读能看到最新写入,或明确失败。
- 可用性(Availability):每个到达未故障节点的请求都能获得非错误响应,但不保证一定是最新数据。
- 分区容错(Partition tolerance):网络分区发生时,系统仍能继续工作。
常见说法“CAP 三选二”容易误导。真实工程中,网络分区无法完全避免,因此问题是:**分区发生期间,选择保证一致性还是保证可用性**。
例如,账户扣款通常倾向 CP:无法确认最新余额时拒绝交易;商品详情、点赞数或浏览量通常倾向 AP:短时间读到旧值,比完全不可用更容易接受。系统在网络正常时仍可以同时拥有较好的一致性和可用性。
## 三、一致性不是只有强一致和最终一致
常见一致性模型包括:
- 线性一致性:操作像在一个全局时间线上瞬间完成,最容易理解,但跨地域代价高。
- 顺序一致性:所有节点看到相同操作顺序,但顺序不一定与真实时间一致。
- 读己之写:用户提交修改后,自己立即能看到。
- 单调读:同一用户不会先看到新值,随后又读到旧值。
- 最终一致性:没有新写入时,各副本最终收敛。
业务设计时不要只写“采用最终一致性”,而要明确:允许多久不一致、哪些字段能旧、用户是否必须读到自己的写入、冲突如何解决、超时后由谁补偿。
## 四、BASE 是工程折中,不是放弃正确性
BASE 通常表示:
- Basically Available:局部故障时仍提供基本能力;
- Soft State:中间状态允许暂时变化;
- Eventually Consistent:经过异步复制或补偿后最终收敛。
最终一致性并不等于“消息发出去就不管了”。完整链路至少需要:
1. 可靠记录待处理事件;
2. 消息可重试;
3. 消费端幂等;
4. 失败进入死信或人工队列;
5. 定期对账发现漏单;
6. 能追踪一次业务请求的完整状态。
## 五、复制、Quorum 与读写取舍
有 N 个副本时,可用 W 表示一次写入需要多少副本确认,用 R 表示一次读取需要查询多少副本。某些系统在满足 W + R > N 时,可增加读到最新值的机会,但这不是脱离具体实现的“强一致公式”:版本比较、并发写冲突、故障检测和修复机制同样重要。
常见复制模型:
- Leader/Follower:写入 Leader,再复制到 Follower。简单清晰,但要处理主节点选举和复制延迟。
- Multi-Leader:多个地域都能写,延迟低,但冲突处理复杂。
- Leaderless:客户端或协调节点同时写多个副本,依赖 Quorum、读修复和反熵。
## 六、生产系统必须设计的六件事
### 1. 超时
任何远程调用都必须有超时。超时值应基于服务延迟分位数、网络预算和完整调用链确定,不能无限等待。
### 2. 重试
只对临时故障和幂等操作重试,使用指数退避并加入随机抖动。严格限制次数和总时长,避免故障时产生重试风暴。
### 3. 幂等
为写请求分配业务幂等键,服务端使用唯一约束、状态机或幂等结果表保证重复请求不重复执行。仅在客户端“避免重复点击”不够。
### 4. 隔离与限流
线程池、连接池、租户配额和舱壁隔离可以阻止一个依赖拖垮整个进程。入口限流要和下游真实容量对齐。
### 5. 顺序与时钟
机器时钟只能用于观测和近似排序,不能直接证明事件因果关系。需要严格顺序时,应使用单分区日志、数据库序列、共识日志或业务版本号。
### 6. 可观测与恢复
至少监控请求量、错误率、延迟分位数、队列积压、复制延迟、选举次数和补偿失败数。每个异步流程都要能回答:当前在哪一步、是否重试过、下一次何时执行、最终是否成功。
## 七、架构选型清单
评审分布式方案时依次回答:
1. 业务允许的数据丢失量(RPO)和恢复时间(RTO)是多少?
2. 哪些操作必须强一致,哪些可以最终一致?
3. 网络分区时是拒绝写入,还是接受写入后再合并?
4. 重复、乱序、延迟和丢失消息分别怎么处理?
5. 节点扩缩容和数据再平衡会不会影响业务?
6. 主节点选举期间,旧主如何防止继续写入?
7. 是否有对账、补偿、回放和人工处置通道?
8. 故障是否经过压测、断网、宕机和时钟漂移演练?
## 八、一个实用原则
先定义业务不变量,再选择技术。例如“一个订单只能支付成功一次”“库存不能卖成负数”属于业务不变量。数据库事务、唯一约束、消息、分布式锁只是实现手段。技术组件失效时,不变量仍然必须成立。
分布式系统没有通用最优解,只有在一致性、可用性、延迟、成本和复杂度之间可解释的取舍。好的架构不是从不失败,而是失败发生时边界清楚、状态可见、操作可恢复。