目录
一、引言:什么是分布式系统
分布式系统(Distributed System)是由多台独立的计算机通过网络连接而成的一个整体系统,对用户而言它表现得就像一台计算机一样。这些计算机在物理上是分散的,在逻辑上是协同的,它们通过消息传递进行通信和协调,共同完成任务。
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像是单个相关系统。其核心特征包括:
1. 资源独立性:每台计算机拥有自己的处理器、内存和资源
2. 通信网络化:节点之间通过消息传递(而非共享内存)通信
3. 并发执行:多个进程并发运行,需要协调与同步
4. 故障独立性:一个节点的故障不会导致整个系统崩溃
5. 透明性:对用户屏蔽系统的分布细节
1.1 分布式系统的核心目标
设计一个分布式系统,本质上是在追求以下几个核心目标,这些目标之间存在天然的张力,架构师需要根据业务场景进行权衡:
- 可扩展性(Scalability):系统可以通过增加节点来线性提升处理能力(横向扩展 / Scale Out),而不是只能靠升级单机硬件(纵向扩展 / Scale Up)。这是分布式系统最核心的价值。
- 容错性(Fault Tolerance):当部分节点发生故障时,系统仍能继续对外提供服务。通常通过冗余(副本)和故障检测机制实现。
- 高可用性(High Availability):系统在绝大部分时间内可被访问,通常用"几个 9"来衡量(如 99.99% 表示全年宕机不超过 52.6 分钟)。
- 透明性(Transparency):屏蔽分布细节,让用户感觉在使用一台计算机。包括访问透明、位置透明、迁移透明、复制透明、并发透明、故障透明等。
- 一致性(Consistency):多个副本上的数据保持一致,或在某种约束下达到最终一致。
- 性能(Performance):低延迟、高吞吐。
1.2 分布式系统的核心挑战
分布式系统在带来扩展性和容错性的同时,也引入了一系列单机系统中不存在的难题,这正是后续章节要逐一解决的问题:
- 网络延迟与不可靠:消息可能丢失、乱序、重复或延迟到达。网络分区(Partition)会导致节点之间无法通信。
- 部分失效(Partial Failure):不同于单机系统要么全好要么全坏,分布式系统中部分节点可能正常、部分故障,处理这种"半死不活"的状态极其困难。
- 并发与同步:多个节点并发读写共享数据,需要协调机制保证正确性,但又不能过度牺牲性能。
- 状态一致性:数据存在多个副本时,如何保证它们一致?强一致性会牺牲可用性,弱一致性又会带来用户体验问题。
- 分布式事务:跨多个节点的操作如何保证原子性?经典的两阶段提交存在阻塞问题。
- 时钟与顺序:不同节点的物理时钟存在偏差,无法直接依赖时间戳判断事件先后,需要逻辑时钟(如 Lamport 时钟、向量时钟)。
- 脑裂(Split-Brain):网络分区导致集群分裂为多个子集群,各自选举出 Leader,可能造成数据冲突。
分布式系统初学者常犯的"九大谬误":1. 网络可靠;2. 延迟为零;3. 带宽无限;4. 网络安全;5. 拓扑不变;6. 管理员只有一个;7. 传输成本为零;8. 网络同构。
软考常考:理解这些谬误是设计健壮分布式系统的前提,"不要假设网络是可靠的"是分布式设计的金科玉律。
二、CAP 定理
CAP 定理是分布式系统最基础的理论之一,由 Eric Brewer 于 2000 年提出,2002 年由 Gilbert 和 Lynch 证明。它揭示了分布式系统在一致性、可用性、分区容错性三者之间的根本取舍。
在一个分布式系统中,以下三个特性最多只能同时满足两个,不可能三者兼顾:
· C(Consistency,一致性):所有节点在同一时刻看到相同的数据
· A(Availability,可用性):系统持续可用,每个请求都能在有限时间内获得非错误响应
· P(Partition Tolerance,分区容错性):当网络发生分区(节点间消息丢失)时,系统仍能运行
2.1 三个特性的精确定义
- 一致性(Consistency):在 CAP 中特指线性一致性(强一致性)。即一次读操作总能返回最近一次写操作的结果,所有节点视图一致。注意这与 ACID 中的 C(事务一致性)含义不同。
- 可用性(Availability):系统中每一个未故障的节点,对于每一个收到的请求,都必须能在有限时间内返回非错误响应(不允许超时或拒绝服务)。
- 分区容错性(Partition Tolerance):当网络在节点之间丢失(或延迟)任意数量的消息时,系统仍能继续工作。在真实分布式系统中,网络分区不可避免,因此P 实际上必须被保证,真正的取舍是在 C 和 A 之间。
2.2 为什么三者不能同时满足
直觉上理解:当网络发生分区时,节点 A 和节点 B 无法通信。此时如果用户向 A 写入数据 x=1,再向 B 读取 x:
- 若要保证一致性 C:B 必须拒绝读取(因为无法从 A 同步最新值),这就牺牲了可用性 A。
- 若要保证可用性 A:B 必须返回一个值(可能是旧值),这就牺牲了一致性 C。
- 而分区 P 是客观存在的网络状况,无法"不发生",只能选择是否容忍。所以工程实践中 CP 和 AP 是主流选择,CA 在分布式下不可行。
2.3 CP / AP / CA 系统举例
| 选择 | 含义 | 典型系统 | 适用场景 |
|---|---|---|---|
| CP | 保证一致性 + 分区容错,分区时牺牲可用性 | ZooKeeper、etcd、HBase、MongoDB(默认) | 金融账户、配置中心、分布式锁、元数据存储 |
| AP | 保证可用性 + 分区容错,分区时牺牲强一致 | Eureka、Cassandra、DynamoDB、Riak | 社交 feed、购物车、缓存、注册中心 |
| CA | 保证一致性 + 可用性,不允许分区(即单机) | 单机 MySQL、单机 Redis | 单机数据库(不存在网络分区问题) |
假设注册中心集群 5 个节点,因网络故障分裂为 {A,B} 与 {C,D,E} 两部分。
· ZooKeeper(CP):分区发生后,少数派 {A,B} 因不满足 Quorum(多数)会停止对外服务,保证已注册数据绝对一致;服务发现可能中断。
· Eureka(AP):两个分区都继续提供注册与发现服务,各自接受新注册,分区恢复后再异步合并。短时间可能出现脏数据,但服务发现"永远可用"。
结论:服务发现场景更看重可用性(找不到服务比返回错数据更严重),所以 Eureka、Nacos(AP 模式)常被选用;而分布式协调场景更看重一致性,故用 ZooKeeper、etcd。
三、BASE 理论
BASE 理论是对 CAP 中 AP 方案的工程化延伸,由 Dan Pritchett 提出,是大规模互联网系统的实践准则。它主张放弃强一致性,转而追求最终一致性,以换取高可用与高性能。
· B(Basically Available,基本可用):在故障情况下允许损失部分可用性(响应时间变长、降级服务、限流),但核心功能仍可用
· S(Soft state,软状态):允许系统存在中间状态,且该状态不影响系统整体可用性(如数据同步中的"副本不一致"状态)
· E(Eventually consistent,最终一致性):系统保证在没有新的更新操作时,所有副本最终会达到一致状态
3.1 最终一致性的变体
最终一致性并非铁板一块,根据保证强度的不同,可细分为以下几种:
- 因果一致性(Causal Consistency):有因果关系的写操作被所有节点按因果顺序看到。
- 读己之所写(Read Your Writes):用户自己写入的数据,自己后续一定能读到。
- 会话一致性(Session Consistency):在同一会话内保证读己之所写。
- 单调读一致性(Monotonic Read):用户一旦读到某个值,不会再读到比它更旧的值。
- 单调写一致性(Monotonic Write):同一来源的写操作按顺序被所有节点看到。
3.2 BASE 与 CAP 的关系
BASE 是 CAP 中 AP 方案的具体落地。CAP 是理论,告诉我们"分区时必须在 C 和 A 之间二选一";BASE 是工程方法,告诉我们"选了 A 之后如何把 C 损失降到最低"——通过软状态和最终一致性,在可用性与一致性之间找到一个可接受的平衡点。
3.3 ACID 与 BASE 对比
| 维度 | ACID | BASE |
|---|---|---|
| 一致性 | 强一致性 | 最终一致性 |
| 可用性 | 较低(锁定资源) | 高 |
| 性能 | 较低 | 高 |
| 扩展性 | 差(难水平扩展) | 好(可水平扩展) |
| 适用场景 | 金融核心交易、库存 | 社交、电商、互联网应用 |
| 典型系统 | MySQL、Oracle | Cassandra、DynamoDB |
| 对应 CAP | 偏向 CP | 偏向 AP |
ACID = "原一隔持"(原子、一致、隔离、持久)
BASE = "基软最"(基本可用、软状态、最终一致)
关系:BASE 是 AP 的工程实践,ACID 是传统单机数据库事务标准。
四、分布式一致性算法
一致性算法解决的核心问题是:让多个节点对某个值(提案/日志)达成一致。这是构建分布式协调服务、分布式锁、状态机复制(State Machine Replication)的基石。工业界最知名的两个算法是 Paxos 和 Raft。
4.1 Paxos 算法
Paxos 由图灵奖得主 Leslie Lamport 于 1990 年提出,是分布式一致性算法的"鼻祖",被认为是正确性最严格的算法,但以难以理解著称。Google Chubby、ZooKeeper 的 ZAB 协议都源自 Paxos 思想。
4.1.1 三种角色
- Proposer(提议者):提出提案(Proposal),提案包含一个全局递增的提案编号和提议值。
- Acceptor(接受者):对提案进行投票,决定是否接受。集群中通常部署奇数个 Acceptor(如 3、5、7)。
- Learner(学习者):不参与投票,只从 Acceptor 学习已达成一致的提案值。
4.1.2 Basic Paxos 流程
Basic Paxos 通过两个阶段达成一致:
- 阶段一:Prepare(准备)
- Proposer 选定一个提案编号 N,向所有 Acceptor 发送
Prepare(N)。 - Acceptor 收到 Prepare(N) 后:若 N 大于已承诺的所有编号,则承诺不再接受编号小于 N 的提案,并返回已接受的最高编号提案(若有);否则拒绝。
- Proposer 选定一个提案编号 N,向所有 Acceptor 发送
- 阶段二:Accept(接受)
- 若 Proposer 收到多数派(Quorum,即过半) Acceptor 的 Promise,则发送
Accept(N, V),其中 V 是 Promise 中返回的最高编号提案值(若没有,则用自己的值)。 - Acceptor 收到 Accept(N,V) 后,若未违背承诺,则接受该提案,并通知所有 Learner。
- 若 Proposer 收到多数派(Quorum,即过半) Acceptor 的 Promise,则发送
· 难理解:Lamport 用故事叙述,证明复杂,被戏称"两个学家也搞不懂"
· 难实现:Basic Paxos 只对单值达成一致,多值需 Multi-Paxos,工程实现细节多
· 活锁:多个 Proposer 互相抢占编号可能导致永远无法达成一致(需引入 Leader 优化)
· 工程上多以 Multi-Paxos + Leader 优化形式落地(如 Google Chubby、Spanner)
4.2 Raft 算法
Raft 由 Stanford 的 Diego Ongaro 在 2014 年提出,目标是"可理解的一致性算法"。它在正确性与 Paxos 等价的前提下,通过分解问题、强化 Leader 角色、约束状态空间,大幅降低了理解和实现难度。etcd、Consul、TiKV 等知名系统都采用 Raft。
4.2.1 三种角色与状态转换
- Follower(跟随者):被动接收 Leader 的日志和心跳。所有节点启动时都是 Follower。
- Candidate(候选人):Follower 在选举超时后未收到心跳,转变为 Candidate,发起选举。
- Leader(领导者):获得多数派选票的 Candidate 成为 Leader,负责处理所有客户端请求、日志复制和心跳。
4.2.2 日志复制流程
Raft 的核心机制是日志复制:客户端请求 → Leader 写入本地日志 → 复制到所有 Follower → 多数派确认后提交 → 响应客户端。
4.2.3 Raft 的三个子问题
- Leader 选举:通过 Term 和随机化选举超时避免分票,保证一个 Term 最多一个 Leader。
- 日志复制:Leader 接收请求并复制日志,多数派确认后提交,保证已提交日志不丢失。
- 安全性(Safety):通过"Leader 完整性"约束(只有日志最完整的 Candidate 才能当选),保证状态机一致性。
4.3 Paxos 与 Raft 对比
| 维度 | Paxos | Raft |
|---|---|---|
| 提出时间 | 1990(Lamport) | 2014(Ongaro) |
| 核心角色 | Proposer/Acceptor/Learner | Leader/Follower/Candidate |
| 角色对称性 | 多 Proposer 平等 | 强 Leader 模型 |
| 可理解性 | 难(论文晦涩) | 易(设计目标即"可理解") |
| 工程实现 | 复杂(Multi-Paxos 无标准) | 清晰(有完整规范) |
| 典型系统 | Chubby、Spanner、ZAB | etcd、Consul、TiKV |
| 消息复杂度 | 两阶段 | 两阶段(正常态) |
| 选主 | 不强制 Leader | 强制 Leader,简化流程 |
etcd 是 Kubernetes 的核心组件,用于存储集群元数据和配置。它采用 Raft 协议保证一致性:
· 通常部署 3 或 5 个节点(容忍 1 或 2 个节点故障)
· 所有写请求都由 Leader 处理,非 Leader 节点收到写请求会转发给 Leader
· 写操作需要多数派确认才提交,故 3 节点集群写入性能约等于单节点,但能容忍 1 节点宕机
· 读操作可由任意节点处理(默认 Linearizable Read 经 Leader 确认)
这就是为什么 etcd 是 CP 系统——牺牲部分可用性换取强一致性。
五、分布式锁
在单机系统中,可以用语言提供的互斥量(Mutex)保护临界区。但在分布式系统中,多个进程运行在不同机器上,无法使用单机锁,于是需要分布式锁——一种跨进程、跨机器的互斥机制。
1. 互斥性:任意时刻只有一个客户端持有锁
2. 避免死锁:持有锁的客户端崩溃后,锁要能自动释放(设置过期时间 / 会话)
3. 可重入性(可选):同一客户端可多次获取同一把锁
4. 公平性(可选):按请求顺序获取锁
5. 高可用:锁服务自身要可靠
5.1 基于 Redis 的分布式锁
最经典的 Redis 分布式锁基于 SETNX(Set if Not eXists)。现代写法是:SET key value NX PX 30000,即"不存在则设置,并带 30 秒过期"。
- value 用 UUID:标识锁的持有者,防止误删别人的锁。
- 释放锁用 Lua 脚本:先比较 value 再删除,保证"判断+删除"原子性。
- 过期时间要设置:防止持有者崩溃导致死锁,但时间需大于业务执行时间。
Redlock 算法(Antirez 提出):为解决 Redis 主从异步复制导致锁丢失问题,在多个(通常 5 个)独立 Redis 实例上同时加锁,获得多数派(3 个)成功且耗时小于锁有效期,才认为加锁成功。
5.2 基于 ZooKeeper 的分布式锁
ZooKeeper 的锁基于临时顺序节点(Ephemeral Sequential Node)实现,天然解决锁释放和公平性问题:
- 客户端在锁节点下创建临时顺序节点
/lock/node-00000001。 - 获取锁节点下所有子节点,判断自己是否是序号最小的;若是,则获得锁。
- 若不是,则监听前一个节点(比自己序号小且最大的)的删除事件(避免羊群效应)。
- 当前一个节点被删除时,自己被唤醒,再次检查是否最小,若最小则获得锁。
- 客户端会话断开时,临时节点自动删除,锁自动释放。
5.3 基于数据库的分布式锁
- 唯一索引法:建一张锁表,对方法名加唯一索引,插入成功即获锁,删除即释放。简单但性能差、无阻塞、需处理过期。
- 悲观锁(for update):在事务中使用
SELECT ... FOR UPDATE,利用行锁互斥。需注意死锁和锁等待超时。 - 乐观锁(版本号):表中加 version 字段,更新时
WHERE version=?。适合冲突少的场景。
5.4 三种实现方式对比
| 维度 | Redis | ZooKeeper | 数据库 |
|---|---|---|---|
| 性能 | 高(内存) | 中 | 低 |
| 可靠性 | 中(Redlock 提升) | 高(CP + 会话) | 中(依赖 DB HA) |
| 公平性 | 非公平 | 公平(顺序节点) | 非公平/可加版本 |
| 自动释放 | 过期时间 | 会话失效自动删 | 需事务/过期清理 |
| 实现复杂度 | 中 | 高 | 低 |
| 适用场景 | 高并发抢购、限流 | 配置/协调、强一致 | 低并发、轻量 |
用户点击"支付"按钮可能因网络卡顿被连点多次,导致重复扣款。解决方案:在支付接口加分布式锁——以
order_id 为 key,Redis 锁(适合高并发)或 ZK 锁(强一致)保证同一订单同时只有一个支付请求进入核心逻辑。结合数据库的幂等性设计(订单状态机:未支付→支付中→已支付),即使锁失效,状态校验也能兜底。
六、分布式事务
当一个业务操作涉及多个数据库或多个服务时,单机的本地事务无法保证跨节点的原子性,于是需要分布式事务。它是微服务架构下最难解决的问题之一,本质上是在一致性、可用性、性能之间权衡。
6.1 两阶段提交(2PC)
2PC(Two-Phase Commit)是经典的强一致性分布式事务方案,引入一个协调者(Coordinator)统一调度多个参与者(Participant)。
- 阶段一:Prepare(准备)。协调者询问所有参与者能否提交,参与者执行事务操作但不提交,将 Undo/Redo 写入日志,回复 Yes/No。
- 阶段二:Commit/Rollback(提交/回滚)。若所有参与者都 Yes,协调者发送 Commit;任一 No,则发送 Rollback。参与者执行后释放资源。
· 同步阻塞:参与者锁定资源期间一直阻塞,整个事务期间其他事务无法访问
· 协调者单点故障:协调者宕机后参与者一直阻塞锁定资源
· 超时不确定性:参与者收到 Commit 前宕机,恢复后无法确定事务状态
· 数据不一致:Commit 阶段部分参与者收不到消息,导致部分提交部分未提交
6.2 三阶段提交(3PC)
3PC 在 2PC 基础上增加一个 CanCommit 阶段,并把 Prepare 拆为 PreCommit,最终 DoCommit。引入超时机制:参与者超时未收到指令会自动提交(基于"大概率会提交"的假设),缓解阻塞。
6.3 TCC(Try-Confirm-Cancel)
TCC 是一种业务层面的两阶段提交,要求每个服务为每个操作提供三个接口:Try(资源预留)、Confirm(确认执行)、Cancel(取消释放)。相比 2PC 锁定资源,TCC 由业务自行管理资源,性能更高。
TCC 优点
- 业务层两阶段,不锁数据库,性能高
- Confirm/Cancel 幂等,可重试
- 最终一致性,可用性好
TCC 缺点
- 业务侵入大,每个操作需实现三个接口
- 需保证 Confirm/Cancel 幂等性
- 需要处理空回滚、悬挂问题
- 开发成本高
6.4 Saga 模式
Saga 将一个长事务拆分为多个本地事务 T1、T2...Tn,每个 Ti 都有对应的补偿事务 Ci。若某步失败,则反向执行已完成步骤的补偿,最终达到一致。Saga 分两种协调方式:
- 编排式(Orchestration):由中央协调器统一调度,逻辑集中,适合复杂流程。
- 协同式(Choreography):各服务通过事件驱动,去中心化,适合简单流程。
6.5 本地消息表
本地消息表是一种最终一致性方案:业务数据和消息数据放在同一个数据库,在本地事务中同时写入业务表和消息表,保证业务与消息原子性。然后由后台任务轮询消息表,将消息投递到 MQ,下游消费后回调确认。它是可靠消息最终一致性的典型实现。
6.6 各种分布式事务方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 低 | 中 | 传统数据库跨库 |
| 3PC | 强一致 | 低 | 高 | 理论改进,少用 |
| TCC | 最终一致 | 高 | 高 | 高并发金融交易 |
| Saga | 最终一致 | 高 | 中 | 长流程业务编排 |
| 本地消息表 | 最终一致 | 高 | 低 | 异步解耦场景 |
| 事务消息(RocketMQ) | 最终一致 | 高 | 中 | 电商下单+异步 |
下单流程涉及订单服务、库存服务、支付服务三个独立数据库。
· 强一致方案(2PC):性能差,互联网基本不用
· TCC:Try 阶段冻结库存 + 创建订单(待支付) + 冻结额度;Confirm 全部扣减;Cancel 全部回滚。适合秒杀
· Saga:T1 创建订单 → T2 扣库存 → T3 支付;若 T3 失败,反向执行 C2(回库存)→C1(取消订单)
· 事务消息(RocketMQ):订单服务发送"半消息"→执行本地事务→提交/回滚消息→库存服务消费扣减。最终一致,最常用
选型:互联网电商主流是 事务消息 + Saga,金融核心用 TCC。
七、负载均衡
负载均衡(Load Balancing)是将请求分发到多台后端服务器的技术,目标是提高吞吐、避免单点过载、提升可用性。按位置可分为服务端负载均衡和客户端负载均衡。
7.1 服务端 vs 客户端负载均衡
- 服务端负载均衡:在服务端部署独立的负载均衡器(硬件 F5、软件 Nginx/LVS/Haproxy),客户端只访问 LB,由 LB 转发到后端。客户端无感知后端列表。
- 客户端负载均衡:客户端从注册中心获取服务列表,自行选择一个节点调用(如 Spring Cloud Ribbon、LoadBalancer)。无需独立 LB,但客户端逻辑复杂。
7.2 常见负载均衡算法
- 轮询(Round Robin):依次分配,简单公平,但未考虑服务器性能差异。
- 加权轮询(Weighted RR):按服务器权重分配,性能强的多分配。Nginx 默认。
- 最少连接(Least Connections):分配给当前连接数最少的服务器,适合长连接。
- IP Hash:对客户端 IP 哈希,同一 IP 固定访问同一服务器,可做会话保持。
- 一致性哈希(Consistent Hash):解决节点增减时大量缓存失效问题,详见第十章。
- 随机(Random):随机选一个,简单但可能不均。
7.3 算法对比
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 轮询 | 依次分发 | 简单 | 忽略性能差异 |
| 加权轮询 | 按权重分发 | 考虑性能差异 | 权重需手动维护 |
| 最少连接 | 选连接数最少 | 自适应负载 | 计算开销略大 |
| IP Hash | IP 哈希固定 | 会话保持 | 节点变化导致大量迁移 |
| 一致性哈希 | 环状哈希空间 | 节点增减影响小 | 需虚拟节点均衡 |
八、分布式 ID 生成
分库分表后,单表自增 ID 失效,需要全局唯一 ID 生成方案。一个合格的分布式 ID 应满足:全局唯一、趋势递增、高可用、高性能。
8.1 常见方案
- UUID:128 位随机数,无需中心节点。优点:本地生成、无碰撞。缺点:无序(导致 B+ 树索引页分裂严重)、过长(36 字符)、无业务含义。
- 雪花算法(Snowflake):Twitter 提出,64 位 long 型 ID,由时间戳 + 工作机器 ID + 序列号组成。趋势递增、高性能、本地生成。
- 号段模式(数据库 Segment):数据库存储 max_id 和 step,应用每次取一个号段(如 1-1000),用完再取。美团 Leaf-segment 即此方案。性能高、可读。
- Redis 自增:利用
INCR命令,性能高但依赖 Redis 可用性,且持久化可能丢号。 - ZooKeeper 顺序节点:可靠但性能差,少用。
8.2 方案对比
| 方案 | 唯一性 | 有序性 | 性能 | 依赖 |
|---|---|---|---|---|
| UUID | 极高 | 无序 | 高 | 无 |
| Snowflake | 高 | 趋势递增 | 极高 | WorkerID 分配 |
| 号段模式 | 高 | 递增 | 高 | 数据库 |
| Redis INCR | 高 | 递增 | 高 | Redis |
| ZooKeeper | 高 | 递增 | 低 | ZK |
· Snowflake 64 位分配:1 符号位 + 41 时间戳 + 10 机器 + 12 序列
· 趋势递增 ≠ 严格递增(同一毫秒内按序列递增,跨毫秒可能小于上一毫秒最后值)
· 工业界主流:美团 Leaf(号段+Snowflake 双 buffer)、百度 UidGenerator、滴滴 Tinyid
九、CDN 与缓存架构
缓存是分布式系统提升性能、降低数据库压力的核心手段。从浏览器到数据库,每一层都可以缓存,构成多级缓存架构。
9.1 CDN 概念与架构
CDN(Content Delivery Network,内容分发网络)通过在各地部署边缘节点,将静态内容缓存到离用户最近的节点,使用户就近获取数据,降低延迟、减轻源站压力。其核心是 DNS 智能调度:用户请求域名时,DNS 返回距离用户最近的 CDN 节点 IP。
9.2 多级缓存架构
大型互联网系统通常采用多级缓存,请求自上而下逐层命中,只有所有缓存未命中才访问数据库:
- 浏览器缓存:通过 Cache-Control、Expires、ETag 控制,减少请求次数。
- CDN 缓存:静态资源(图片、JS、CSS)缓存在边缘节点。
- Nginx 缓存:反向代理层缓存,proxy_cache 共享内存或磁盘。
- 本地缓存(应用内):如 Guava Cache、Caffeine,进程内缓存,极低延迟。
- 分布式缓存:如 Redis Cluster、Memcached,跨进程共享。
- 数据库:最后防线,自身也有 Buffer Pool 缓存。
9.3 缓存三大经典问题
缓存并非银弹,使用不当会引发三类典型故障:穿透、击穿、雪崩。软考高频考点,必须能区分并给出解决方案。
9.3.1 缓存穿透(Cache Penetration)
指查询一个根本不存在的数据,缓存和数据库都没有,每次请求都打到数据库。常见于恶意攻击(用大量不存在的 ID 请求)。
解决方案:
- 缓存空值:数据库查不到也缓存 null(设短过期时间),下次同样请求直接返回。
- 布隆过滤器(Bloom Filter):在缓存前加一层布隆过滤器,请求先过过滤器,不存在则直接拒绝。
9.3.2 缓存击穿(Cache Breakdown)
指一个热点 Key 在缓存过期的瞬间,大量并发请求同时打到数据库,造成 DB 瞬时压力激增。区别于穿透(数据不存在),击穿的数据是存在的,只是缓存恰好失效。
解决方案:
- 互斥锁(Mutex):缓存未命中时,只允许一个线程查 DB 并回填缓存,其他线程等待或重试。
- 热点 Key 永不过期:逻辑过期,后台异步更新。
9.3.3 缓存雪崩(Cache Avalanche)
指大量 Key 在同一时间集中过期,或者 Redis 整体宕机,导致大量请求同时打到数据库,引发 DB 雪崩式崩溃。区别于击穿(单个热点),雪崩是大面积失效。
解决方案:
- 过期时间加随机值:TTL = base + random,避免同时过期。
- Redis 集群高可用:主从 + 哨兵 + Cluster,避免整体宕机。
- 多级缓存:本地缓存兜底。
- 限流降级:DB 压力大时限流,返回降级数据。
· 穿透:数据不存在 → 缓存空值 + 布隆过滤器
· 击穿:单个热点过期 → 互斥锁 + 永不过期
· 雪崩:大量 Key同时过期 → 随机 TTL + 高可用
一句话:穿透是"没有",击穿是"一个崩",雪崩是"一片崩"。
十、一致性哈希
一致性哈希(Consistent Hashing)由 MIT 的 Karger 等人于 1997 年提出,主要解决分布式缓存节点增减时大量 key 重新映射的问题,是负载均衡和分片的核心算法。
10.1 为什么需要一致性哈希
传统哈希取模法:hash(key) % N,N 为节点数。当 N 变化(增减节点)时,几乎所有 key 都会重新映射,导致大面积缓存失效,瞬间击穿数据库。一致性哈希将哈希空间组织成环,节点增减只影响相邻区间的 key,迁移量最小。
10.2 节点增减的影响分析
一致性哈希的关键性质:当增加或删除一个节点时,只有该节点顺时针相邻区间的 key 需要迁移,其他 key 保持不变。迁移量约为 1/N(N 为节点数),远优于取模法的全部迁移。
Redis Cluster 没有直接用一致性哈希环,而是用哈希槽(Hash Slot):固定 16384 个槽,每个 key 经 CRC16 哈希后对 16384 取模定位槽位,槽位均匀分配到各节点。
· 扩容:新节点加入时,从现有节点迁移部分槽位(slot 迁移)
· 缩容:先迁移该节点所有槽位到其他节点,再下线
· 优势:槽位离散、迁移可在线进行、客户端可用
MOVED重定向这本质是预分配的一致性哈希变体,避免了虚拟节点映射开销,是工业界优秀实践。
十一、分布式系统实战案例:电商秒杀系统
秒杀是分布式系统综合能力的"试金石":瞬时高并发、库存超卖、防刷、支付一致性等问题集中爆发。下面用前面所学理论设计一个秒杀系统。
11.1 整体架构
11.2 关键技术点
限流:令牌桶与漏桶
- 令牌桶(Token Bucket):以固定速率向桶中放令牌,请求消耗令牌,桶满则丢弃。允许突发流量(桶中令牌可瞬时消耗)。适合秒杀。
- 漏桶(Leaky Bucket):请求如水滴入桶,以固定速率漏出。输出速率恒定,平滑流量,不允许突发。
异步削峰
秒杀请求经 Redis 预扣库存成功后,不直接写库,而是发送消息到 MQ,由下游消费者异步创建订单、扣减 DB 库存。这样将瞬时高并发转为平稳消费,保护数据库。
库存扣减防超卖
使用 Redis 的 Lua 脚本原子执行"判断库存 + 扣减":if stock > 0 then decr stock end。Lua 在 Redis 中单线程执行,天然原子,避免超卖。
十二、软考考点总结
分布式系统是系统架构设计师考试的核心重难点,涉及理论、算法、工程实践多个层面。下面梳理高频考点与记忆要点。
12.1 核心理论与公式
1. CAP 定理:C/A/P 三者只能取其二;分布式系统 P 必选,故在 CP 与 AP 间权衡
2. BASE 理论:基本可用 + 软状态 + 最终一致性(AP 的工程实践)
3. Quorum 机制:N 个副本,W 写确认,R 读确认,满足 W + R > N 即可保证强一致
4. FLP 不可能定理:异步网络中,即使只有 1 个节点故障,也无法存在确定性的一致性算法
5. 共识 vs 一致性:共识(Consensus)是多节点就值达成一致;一致性(Consistency)是数据状态一致
12.2 算法与协议对比
| 主题 | 核心要点 | 典型系统 |
|---|---|---|
| Paxos | Proposer/Acceptor/Learner;两阶段;多数派 | Chubby、Spanner |
| Raft | Leader/Follower;强 Leader;日志复制 | etcd、Consul、TiKV |
| ZAB | ZK 原子广播;类似 Paxos;Leader 选举 | ZooKeeper |
| Gossip | 流行病协议;最终一致;去中心化 | Cassandra、Redis Cluster |
| 2PC | 协调者+参与者;Prepare+Commit;阻塞 | XA 规范 |
| 3PC | CanCommit+PreCommit+DoCommit;超时 | 理论改进 |
| TCC | Try/Confirm/Cancel;业务两阶段 | 蚂蚁 DTX、Hmily |
| Saga | 长事务拆分+补偿;编排/协同 | AWS Step Functions |
12.3 常见题型与答题技巧
- 选择题:CAP/BASE 含义、算法角色、缓存问题区分(穿透/击穿/雪崩)、负载均衡算法特点。重点记区别。
- 简答题:简述 Paxos/Raft 流程、2PC 缺陷、一致性哈希原理。建议画图辅助说明。
- 案例分析题:给定场景(如电商、金融)选择合适方案。答题套路:方案选型 → 原理说明 → 优缺点对比 → 改进建议。
- 论文题:常考"分布式事务/缓存/微服务架构设计"。要求结合实际项目,论点清晰、论据充分。
12.4 记忆口诀汇总
· CAP:"分区必选 P,强一致选 CP,高可用选 AP"
· BASE:"基软最"——基本可用、软状态、最终一致
· Paxos 两阶段:"一准二接"——Prepare/Promise、Accept/Accepted
· Raft 三角色:"跟随-候选-领导",靠任期 Term晋升
· 2PC 缺陷:"阻、单、不"——同步阻塞、协调者单点、数据不一致
· 缓存三问:"穿不存在、击单热点、雪大面积"
· 分布式锁三方案:"Redis 快、ZK 稳、DB 简"
· Snowflake:"1-41-10-12"(符号-时间-机器-序列)
· 一致性哈希:"环上走,顺时针,虚拟节点防倾斜"
· 秒杀设计:"缓存拦、限流削、异步化、原子扣"
12.5 高频易错点
1. CAP 的 C 是线性一致性,不是 ACID 的一致性,二者含义不同
2. BASE 的 C 是最终一致性,弱于 ACID 的强一致性
3. Paxos 多数派指"过半",3 节点需 2 票,5 节点需 3 票
4. Raft 任期单调递增,过期 Leader 收到更高 Term 会自动降级
5. 缓存击穿 vs 雪崩:击穿是"一个热点",雪崩是"大量 Key"
6. 令牌桶 vs 漏桶:令牌桶允许突发,漏桶平滑输出
7. 2PC 的同步阻塞发生在 Prepare 阶段(资源锁定到 Commit)
8. TCC 的 Confirm/Cancel 必须幂等,因为可能被重试
9. 一致性哈希迁移量 1/N,N 为节点数;虚拟节点解决倾斜
10. Redis Cluster 是 16384 槽,不是一致性哈希环
分布式系统的精髓在于权衡(Trade-off):没有完美方案,只有适合场景的方案。架构师的价值在于根据业务对一致性、可用性、性能、成本的优先级,选择并组合恰当的理论与工程实践。希望本文能帮助你在软考和实际工作中建立系统的分布式思维。
1. 先理解理论(CAP/BASE/FLP),再学算法(Paxos/Raft),最后落地工程(锁/事务/缓存)
2. 多画图:CAP 三角、Paxos 时序、一致性哈希环、缓存问题对比图
3. 结合实际项目:把每个概念对应到自己系统的某个组件
4. 论文素材:准备 1~2 个分布式项目(如秒杀、订单系统),按"问题-方案-效果"组织