Skip to content

分布式基础、一致性与复制 ​

分布式系统由多个通过网络协作的节点组成,对外共同提供服务。把单机程序拆到多台机器后,最重要的变化不是“机器更多”,而是出现了部分失败:某些节点或链路出问题时,其他部分仍在运行,而且观察者往往无法立即判断对方是宕机、网络延迟,还是消息丢失。

分布式系统的核心困难 ​

  1. 网络不可靠:消息可能延迟、丢失、重复、乱序,连接成功也不代表请求一定执行成功;
  2. 节点会独立失败和恢复:进程崩溃、机器断电、磁盘损坏,重启后内存状态丢失;
  3. 没有绝对可靠的全局时钟:机器时钟会漂移、回拨,跨节点时间戳不能天然表示因果顺序;
  4. 并发写入:多个节点可能同时修改同一份逻辑数据,需要定义冲突解决规则;
  5. 状态存在多个副本:复制提高可用性和读吞吐,但会引入副本落后、冲突和一致性成本;
  6. 系统需要扩缩容:增加或移除节点时要迁移数据、更新路由,并控制热点和抖动。

因此,分布式设计首先要回答:允许哪些失败、需要多强的一致性、最长能等待多久、失败后能否重试、重复执行是否安全。

常见故障模型 ​

故障含义常见处理
Crash-stop节点崩溃后不再恢复冗余副本、重新选主
Crash-recovery节点崩溃后可能带持久化状态恢复WAL、快照、日志重放
Omission请求或响应丢失超时、重试、幂等
Network partition节点分组之间暂时无法通信Quorum、选主、降级或拒绝写入
Slow node节点未宕机但响应极慢超时、隔离、尾延迟治理
Byzantine节点可能返回任意或恶意结果BFT 协议;普通业务系统通常不采用该模型

超时不是失败证明。收到超时只能说明“期限内没有收到响应”,不能确定对方是否执行过请求。因此写请求重试必须配合请求 ID、幂等键或状态机版本。

时间、顺序与因果关系 ​

物理时钟 ​

系统时间适合展示和粗粒度过期判断,但存在 NTP 校时、漂移和回拨。计算超时、耗时时优先使用单调时钟;不能仅凭两台机器的 wall clock 时间戳判断谁先发生。

Lamport 逻辑时钟 ​

每个节点维护递增计数器:本地事件加一;发送消息携带计数;接收时更新为 max(local, received) + 1。若事件 a 因果先于 b,一定有 L(a) < L(b);反过来不成立,因此 Lamport 时钟可以给出兼容因果关系的全序候选,但不能判断两个事件是否并发。

向量时钟 ​

每个节点维护一组计数。若向量 A 每一维都不大于 B 且至少一维更小,则 A 因果先于 B;若两者互不支配,则事件并发。它能检测冲突,但元数据随参与节点数量增长,成员动态变化时维护成本较高。

复制模型 ​

Leader-Follower ​

写请求先到 Leader,再复制给 Follower:

text
Client → Leader → Followers
  • 单 Leader 容易定义写入顺序;
  • Follower 可承担只读请求,但可能读到旧数据;
  • Leader 故障需要选主;旧 Leader 恢复后必须防止继续写入造成脑裂;
  • 同步复制延迟更高但数据更安全,异步复制延迟更低但故障时可能丢失尚未复制的写入。

Multi-Leader ​

多个节点都接受写入,适合跨地域或离线同步,但同一 key 可能在不同 Leader 上并发更新,需要版本、因果关系或业务合并规则解决冲突。简单的“最后写入获胜”依赖时钟,可能覆盖实际更晚发生的修改。

Leaderless ​

客户端或协调节点同时向多个副本读写,通过 N、R、W 的 Quorum 组合获取结果。它避免单 Leader 写入口,但需要处理并发版本、读修复、反熵和临时故障下的副本偏移。

一致性模型 ​

“一致性”不是只有强和弱两档,而是系统对读写可观察顺序的承诺。

模型核心承诺典型理解
线性一致性(Linearizability)每个操作像在调用与返回之间某个瞬间原子完成,且尊重真实时间先后写成功后,后续读必须看到该写或更新值
顺序一致性所有节点看到同一个全局操作顺序,但不要求尊重真实时间顺序统一,但可能与墙上时钟先后不同
因果一致性有因果关系的操作按相同顺序可见,并发操作顺序可不同回复不能先于原消息可见
最终一致性停止新写后,副本最终收敛收敛前允许读到旧值
读己之写一个客户端能看到自己已经成功的写入登录态、用户设置常需要
单调读同一客户端不会先看到新版本、后看到旧版本可通过会话粘滞或版本路由实现

线性一致性比“事务串行化”范围更广:前者描述并发对象的实时可见性,后者通常描述数据库事务等价于某个串行执行顺序。系统可能提供可串行化事务,但如果不尊重真实时间,仍不一定线性一致。

并发冲突如何合并 ​

多 Leader 或 Leaderless 系统可能产生并发版本,必须定义合并规则:

  • Last-Write-Wins:选择时间戳较大的版本,简单但依赖时钟,并可能静默覆盖有效写入;
  • 保留 siblings:用版本向量识别并发版本,交给业务或客户端合并;
  • 业务状态机:按领域规则合并,例如集合并集、状态优先级或人工冲突处理;
  • CRDT:把状态和合并操作设计为满足交换律、结合律、幂等性,使副本在任意顺序重复合并后收敛。

CRDT 适合计数器、集合等可形式化合并的数据类型,但“副本最终收敛”不代表自动满足库存不能为负、用户名唯一等跨对象业务不变量。

CAP 应该如何回答 ​

CAP 讨论发生网络分区 P 已经出现时,系统无法同时保证:

  • C(Consistency):CAP 语境通常指线性一致性;所有成功操作像访问一个最新副本;
  • A(Availability):每个到达未故障节点的请求最终都得到非错误响应,不允许因为无法联系多数派而拒绝;
  • P(Partition tolerance):系统在节点间消息丢失或无限延迟时仍按设计运行。

真实分布式系统不能选择网络永不分区,因此重点是分区期间如何在 C 与 A 之间取舍:

  • CP:少数派拒绝写入或读写,避免两个分区分别产生冲突状态;
  • AP:各分区继续服务,允许暂时不一致,恢复后合并冲突。

“CA 系统”通常只在不考虑分区的模型或单节点范围内有意义。CAP 也不是说系统平时只能二选一;没有分区时可以同时具有一致性和可用性,而且不同接口可以采用不同策略。

PACELC ​

CAP 只描述分区期。PACELC 补充:若发生分区(P),在可用性(A)和一致性(C)之间取舍;否则(E,Else),仍要在延迟(L)和一致性(C)之间取舍。同步等待更多副本通常提高一致性和持久性,但增加写延迟和尾延迟。

Quorum 读写 ​

设一份数据有 N 个副本,写成功至少等待 W 个副本,读至少查询 R 个副本。常见条件:

text
R + W > N     // 读集合与最近一次成功写集合必有交集
W > N / 2     // 任意两个成功写集合必有交集

例如 N=3, W=2, R=2,一次读至少接触一个接收过成功写入的副本。但集合有交集并不自动等于线性一致性,还需要版本比较、并发写冲突处理、失败节点恢复、读修复和严格的成功定义。

Quorum 的选择体现读写权衡:

  • W 大、R 小:写慢、读快;
  • W 小、R 大:写快、读慢;
  • 只读一个异步 Follower:延迟低,但可能陈旧;
  • 允许 sloppy quorum 时,请求可能写到临时代替节点,简单公式不能直接提供严格保证。

常见面试追问 ​

“主从复制是否就是强一致?” ​

不是。异步主从只能保证副本最终追上;若读 Follower,可能看到旧数据。即使写 Leader,也要说明写成功是仅写入内存、写入本地持久化,还是已复制到多数派。

“最终一致是否意味着随便覆盖?” ​

不是。系统仍要定义版本和合并规则,例如版本号、向量时钟、CRDT 或业务状态机。最终一致只承诺副本最终收敛,不规定冲突如何自动正确解决。

“网络超时后可以直接重试吗?” ​

读通常可安全重试;写必须假设原请求可能已经执行。应使用幂等键、唯一约束、条件更新或可去重的请求状态。

面试中的简洁回答 ​

分布式系统的难点是部分失败、网络不可靠、没有可靠全局时钟,以及同一状态存在多个副本。复制提高可用性和吞吐,但需要选择一致性模型。CAP 不是平时三选二,而是网络分区发生时在强一致和持续可用之间取舍;PACELC 还强调无分区时延迟与一致性的权衡。Quorum 通过相交读写集合提高读取最新版本的机会,但要结合版本、冲突解决和故障恢复,不能只背 R + W > N。

关联笔记 ​

使用 Markdown 与 VitePress 构建