分布式系统基础:CAP、一致性哈希与分布式事务

单机玩得转之后,「多台机器怎么像一台机器一样可靠」就是新的战场。本文是纯理论 + 工程折中的底座篇,系统设计题、消息队列、MySQL 主从、Redis 集群 都站在这块地基上。


1. CAP 与 BASE:先接受不完美

CAP 定理:分布式系统在以下三者中最多同时满足两个:

  • C(Consistency)一致性:所有节点在同一时刻看到相同数据
  • A(Availability)可用性:每个请求都能得到(非错误)响应
  • P(Partition Tolerance)分区容忍:网络分区(节点间断联)发生时系统仍能工作

关键认知:网络分区在分布式系统中是必然事件,P 不能舍弃——所以真实的选择永远在「分区发生时,要 C 还是要 A」:

取舍含义典型
CP分区时拒绝服务,保一致性ZooKeeper、etcd、分布式锁的强一致替代品
AP分区时继续服务,容忍短暂不一致Eurekaopen in new window、多数缓存集群、DNS

日常工程更多谈 BASE(Basically Available 基本可用 / Soft state 软状态 / Eventually consistent 最终一致):牺牲强一致换取可用与性能,事后收敛——你的缓存一致性策略、主从复制延迟、MQ 的最终一致全是 BASE 思想。

面试表述要点:CAP 不是「三选二」的静态配置,而是分区发生那一瞬间的决策;未分区时(多数时间)C 和 A 可以兼得。


2. 一致性哈希:数据分片的经典算法

问题:按 hash(key) % N 分片,扩/缩容时几乎全部 key 要迁移。

一致性哈希把哈希空间组织成环(0 → 2³²):

flowchart TB
  R["哈希环"] --- A["节点 A<br/>负责 240°~0°"] --- B["节点 B<br/>负责 120°~240°"] --- C["节点 C<br/>负责 0°~120°"] --- R
  K1["key1 → 顺时针找第一个节点"] -.-> B
  • 数据与节点都映射到环上,数据归属顺时针遇到的第一个节点
  • 节点下线:只迁移它的后继区间;节点从 N 变 N+1,只动约 1/N 的数据
  • 虚拟节点:物理节点映射成上百个虚拟点打散在环上,解决数据倾斜(节点少时原始方案分布不均)
  • 实际应用:Redis Cluster 用的哈希槽(16384 槽)是同思想的简化版、负载均衡、分布式缓存

3. 分布式事务:四种方案与适用边界

跨服务/跨库的写操作如何保持原子?按「一致性强度 → 复杂度」排序:

3.1 2PC(两阶段提交)—— 强一致,同步阻塞

sequenceDiagram
  participant C as 协调者
  participant A as 参与者A
  participant B as 参与者B
  C->>A: 阶段1: prepare
  C->>B: 阶段1: prepare
  A-->>C: 可以提交
  B-->>C: 可以提交
  C->>A: 阶段2: commit
  C->>B: 阶段2: commit
  • 数据库/XA 事务、跨库 SDK 常见;协调者单点、同步阻塞、网络抖动放大故障——适合数据库层,不适合微服务业务层

3.2 TCC(Try-Confirm-Cancel)—— 业务层强一致

每个参与方实现三个接口:Try 预留资源(冻结库存)→ Confirm 确认扣减 → Cancel 释放。性能好、无长锁,但业务侵入极大(每个操作写三遍逻辑 + 空回滚/悬挂处理),适合金融核心链路。

3.3 Saga —— 长流程编排

把事务拆成一串本地事务,每步配一个补偿动作;失败时逆向补偿。编排式(状态机)或协同式(事件驱动)。适合长流程(下单 → 扣库存 → 配送),典型实现 Temporal/Camunda;RocketMQ 的事务消息是它的轻量变体。

3.4 本地消息表 —— 最朴素的最终一致(推荐默认)

1. 业务操作与「待发送消息」在同一个本地事务落库
2. 后台任务扫描消息表投递到 MQ(重试直到成功)
3. 消费方幂等消费(见下节)
  • 不引入新组件、消息不丢,代价是最终一致 + 一张表
  • 大多数「跨库一致性」需求的默认答案,别上来就 TCC

4. 幂等:分布式的第一必修课

重试无处不在(网络超时、MQ at-least-once、用户重复点击),没有幂等,重试就是灾难:

手法实现适用
唯一约束DB 唯一键 / upsert防重复创建,最可靠
去重表消费前记录 msgId,插入冲突即重复通用消费幂等
状态机UPDATE orders SET status='PAID' WHERE status='PENDING',影响行数为 0 即重复有状态流转的业务
乐观锁版本号WHERE version = ?更新类操作
Token 机制先领 token,提交时原子校验删除表单防重复提交

5. 其他必备共识常识

  • Paxos / Raft:多数派投票选主与日志复制。Raftopen in new window 以可理解性胜出——etcd、Redis Sentinel 的选主、MongoDB 副本集选举、TiKV 都是它的实现/变体;读一遍 Raft 论文或看 raft.github.io 的可视化动画,是分布式面试的高性价比投资
  • 分布式锁回顾:Redis 锁的性能 vs etcd/ZK 的强一致,按业务选,见 Redis 分布式锁
  • 限流算法:计数器(突刺)→ 滑动窗口 → 漏桶(恒定速率)→ 令牌桶(允许突发,最常用);单机用内存,集群用 Redis + Lua
  • 时间与顺序:跨机时钟不可信;全序靠逻辑时钟(Lamport)或单点排序(数据库/协调者),别用 System.currentTimeMillis 做跨机比较

6. 检查清单

  • 说得清自己项目里哪个链路是 CP、哪个是 AP,为什么
  • 跨服务的写操作有明确的方案(本地消息表 / Saga / TCC),不是「没有事务」
  • 所有会被重试的接口做过幂等(至少唯一约束或状态机)
  • 分片方案能回答「扩容时数据怎么迁」
  • 分布式锁的失效场景与兜底说得出来

延伸:系统设计题 · 消息队列 · Redis 集群与锁 · K8s 与 etcd

Last Updated:
Contributors: rain9