分布式系统总览
绝大多数工程师第一次碰分布式,是被业务推着上的——单库扛不住、单机内存装不下、机房挂了一次。真正难的不是"加机器",而是加完机器之后,你发现这个系统跟你以前写过的所有系统都不一样:它会"部分坏掉"(一半节点活着一半死了)、它的时间是错的、它的网络会骗你、它的状态散在一堆机器里没人能一眼看完。这一篇先把这条不归路上的全貌看清楚——为什么要上分布式、上完要付什么代价、什么时候根本不该上,以及后续 29 篇要展开的"分布式难题"到底有哪几类。
一句话先记住:分布式系统的本质不是"多机协作",而是"在部分失败的世界里维持不变量"——这句话理解了,后面的 CAP / FLP / Paxos / 2PC 全都有了着力点。单机的故障模型是"全活或全死",分布式的故障模型是"一部分活一部分死,而你不知道是哪一部分"——95% 的诡异 bug 都源自工程师没把这件事放在心上。
一、为什么需要分布式
绝大多数系统第一天都是单机的——一台机器、一个进程、一份数据。单机系统最大的优势是"简单":没有网络、没有部分失败、没有时钟漂移、没有协调开销。能在单机上跑就在单机上跑,这不是落后,这是工程纪律。
那为什么后来还是上了分布式?只有四个真实理由——其余都是借口。
1.1 单机硬件的物理天花板
| 维度 | 单机上限(2025 年商用配置) | 真实业务很快就破 |
|---|---|---|
| CPU | 单机最多 ~256 核,~8 GHz 频率撞天花板十年了 | 实时计算、视频转码 |
| 内存 | 单机最多 ~12 TB DDR5(主板槽位限制) | 大模型推理、图数据库 |
| 磁盘 | 单盘 ~30 TB NVMe,单机 ~500 TB(机箱槽位) | 日志、视频、原始数据 |
| 网络 | 单机 ~400 Gbps 网卡,~100 万 PPS | CDN、IM 长连接 |
| 持久化吞吐 | 单 SSD ~7 GB/s 写,单机 ~30 GB/s | 海量写入业务 |
注意:单 CPU 频率十年没涨——摩尔定律对单核早就死了,性能只能横向加机器。这一条比"内存装不下"更根本——就算你买得起更贵的机器,主频也涨不上去。
1.2 容错:任何一台机器都会挂
单机系统的可用性上限就是单机硬件的可用性——典型服务器 MTBF(平均无故障时间)是 3-5 年,机房环境下年故障率约 2-5%(磁盘、电源、内存条、网卡都可能挂)。
单机可用性:99% (一年挂 87 小时)
两副本 :99.99% (一年挂 52 分钟)
三副本 :99.9999% (一年挂 30 秒)注意这是「理想模型」——实际副本不独立(同机房断电、同版本 bug、同操作员误操作),多副本的真实可用性远低于数学期望。但即便如此,冗余是绕不开的——单机系统不可能达到 99.9% 以上。
1.3 地理分布:用户在全球,光速救不了你
光在光纤里跑 1000 公里需要 5 ms,北京到伦敦单程 ~70 ms,往返 ~140 ms。如果你的服务只部署在北京:
- 北京用户:RTT 几毫秒,体验丝滑
- 伦敦用户:每次请求 140 ms 起步,点一下页面感觉电脑卡了
光速是物理常数,改不了——只能"把服务挪到用户附近"。这就要求:
- 多机房部署(全球 CDN、边缘节点)
- 跨地域数据复制(美国和中国都有一份数据)
- 跨地域一致性协议(怎么让两份数据保持一致)
跨地域分布式系统的难度比单机房高一个数量级——单机房 RTT < 1 ms,跨地域 RTT 几十到几百 ms,这意味着同步协议(Paxos / Raft)的延迟成本完全不同。Spanner 给你跨地域强一致,代价是每次写入 ~100 ms 延迟。
1.4 弹性:流量不是恒定的
互联网业务的流量峰谷比例常常是 10:1 到 100:1——双 11、春晚、突发热点。
日常 QPS: 1 万
峰值 QPS: 100 万
按峰值买单机 → 平时 99% 的算力浪费 → 成本爆炸
按日常买单机 → 峰值挂掉 → 收入归零
按日常买基础容量 + 弹性扩容 → 平时省钱、峰值能扛 → 唯一可行解单机系统没有弹性——你不可能 11 月 1 日插一根内存条,11 月 12 日拔下来。分布式的"加机器" / "缩容"才是弹性的物质基础。
二、分布式的真实代价
上了分布式不是"加几台机器"这么轻松,你付出的代价远比想象中大。这一节把代价摊开——看完心里有数,选型时才不会一拍脑袋上分布式。
2.1 延迟从纳秒涨到毫秒
单机内存访问: ~100 ns
单机 SSD 访问: ~100 μs (1000 倍)
同机房网络往返: ~500 μs (5000 倍)
跨机房网络往返: ~10 ms (10 万倍)
跨地域网络往返: ~100 ms (100 万倍)只要请求一跨进程,延迟就涨 1000 倍以上。原本单机一次函数调用 100 ns,分到两台机器变成 500 μs——业务代码看起来一样,延迟相差 5000 倍。
更可怕的是长尾:同机房 RTT 平均 500 μs,P99 可能 5 ms,P999 可能 50 ms——一次业务请求调用 20 个下游服务,P999 长尾叠加起来直接超时。
2.2 调试复杂度爆炸
单机 bug:开 gdb 单步,改完编译再跑。 分布式 bug:
- 复现难:涉及多节点状态、网络时序、并发顺序,复现一次 bug 可能要重放上百万条消息
- 观测难:日志散在 N 台机器上,没有统一的"系统当前状态"视图
- 推理难:happens-before 关系全乱(详见 06 篇 Lamport 时钟),"哪个操作先发生"这种单机的常识在分布式里根本说不清
Jepsen(知名分布式系统测试项目)发现的几乎所有 bug,单元测试都测不出来——只有在网络抖动 + 节点宕机 + 并发请求三重组合下才会暴露。分布式 bug 是"罕见组合"的 bug,常规测试覆盖不到。
2.3 运维成本指数级上升
| 维度 | 单机 | 分布式(10 节点) |
|---|---|---|
| 部署 | 一次 SSH | 配置管理工具 / K8s |
| 监控 | 看一台机器的 CPU | Prometheus + 服务网格 + 链路追踪 |
| 故障定位 | 一台机器 | 多节点日志聚合 + 分布式追踪 |
| 数据备份 | 一份磁盘 | 多副本一致性、备份策略、恢复演练 |
| 升级 | 停机重启 | 滚动升级、灰度发布、回滚预案 |
| 安全 | 一道防火墙 | 服务间 mTLS、零信任网络 |
人力成本:维护一个 100 节点的分布式系统,至少需要 2-3 个专职 SRE——而单机系统一个开发者业余维护就够了。
2.4 CAP 给你的硬约束
CAP 定理(详见 04 篇)说:分区发生时,一致性 C 和可用性 A 只能二选一。
- 选 C:网络分区时拒绝服务(ZooKeeper / etcd 的选择)
- 选 A:网络分区时允许不一致(Cassandra / DynamoDB 的选择)
这不是技术问题,是物理问题——网络永远会分区,你必须选一边。单机系统没这个困扰:要么全活要么全死,不存在"一半活一半死、互相通信不上"的状态。
2.5 状态分散,事务消失
单机数据库的 ACID 事务是免费的——BEGIN; UPDATE A; UPDATE B; COMMIT;,两个更新原子完成。
分布式下,A 在节点 1、B 在节点 2,你想让它们原子提交?恭喜你进入分布式事务的深坑(详见 20-24 篇):
- 2PC:有协调者宕机风险,会卡死
- Saga:补偿事务,代价是放弃隔离性
- TCC:业务侵入,每个操作要写三遍
- Percolator / Spanner:重量级,延迟高
没有"完美的分布式事务"——所有方案都是在 ACID 的某个字母上妥协。
三、什么时候不该上分布式
工程师最大的虚荣心之一就是"造大系统"——但真实世界里,90% 的业务用单机 + 主从就够了。这一节专门讲"什么时候不该上分布式"——能不上就不上,是工程纪律,不是技术水平不够。
3.1 规模没到
| 指标 | 单机能扛的上限 | 真上分布式的合理时机 |
|---|---|---|
| QPS | ~5 万 (简单接口) | 持续 > 10 万 |
| 数据量 | ~1 TB (热数据) | 持续增长 > 10 TB |
| 用户数 | ~100 万 DAU | > 500 万 DAU |
| 团队规模 | 3-5 个开发 | > 10 个开发 |
90% 的初创公司、内部工具、企业级应用,规模根本到不了上分布式的临界点。强行上分布式 = 用 10 倍的复杂度换 0 倍的好处。
3.2 单机能搞定的"伪需求"
- "我们要做高可用":一主一备 + Keepalived + VIP 漂移就够了,不需要 Raft 集群
- "我们要做读扩展":MySQL 一主三从就够了,不需要 Cassandra
- "我们要做容灾":每天凌晨备份到 S3 + 异地冷备,不需要跨地域强一致
- "我们要解耦":进程内事件总线 / 函数回调就够了,不需要 Kafka
判定标准:如果你能用「单机 + 主从 + 备份」解决,就别上分布式中间件。ZooKeeper / etcd / Kafka / Cassandra 这些重武器,带来的运维成本超过 90% 业务的承受能力。
3.3 团队没准备好
分布式系统需要团队具备:
- 混沌工程能力:能主动断网、kill 节点验证容错
- 可观测性建设:链路追踪、指标体系、日志聚合
- 运维自动化:K8s / Terraform / Ansible
- SRE 文化:错误预算、SLO/SLI、故障复盘
这些能力建立至少需要 6-12 个月。团队没建好就上分布式 = 给自己埋雷,线上事故频率会上升一个数量级。
3.4 业务对延迟极敏感
某些业务单机延迟才达标,分布式必然超时:
- 高频量化交易:单笔决策要求 < 100 μs,任何网络跨进程都不可接受
- 游戏服务器内逻辑:同区玩家交互要求 < 10 ms,强行分布式延迟翻 10 倍
- 嵌入式 / 工业控制:实时性要求亚毫秒级
这类系统的"扩展"靠纵向(更强的单机)+ 业务分片(不同区不同进程),而不是"集群"。
四、分布式系统的标志性问题
分布式系统的"难",集中在五类问题上。这五类后面 29 篇会一一展开,这里先把全貌看清楚——95% 的分布式 bug 最终都归到这五类之一的不变量被破坏。
┌────────────────────────────────────────┐
│ 分布式的五座大山 │
├────────────────────────────────────────┤
│ 1. Partial Failure (部分失败) │
│ 2. Unreliable Network (网络不可靠) │
│ 3. Concurrency (并发) │
│ 4. No Global Clock (时间无全局) │
│ 5. Distributed State (状态分散) │
└────────────────────────────────────────┘4.1 Partial Failure(部分失败)
单机系统:要么全活要么全死——进程崩了所有功能都没了,这反而是个"好"的故障模式,因为状态干净。
分布式系统:节点 A 活、节点 B 死、节点 C 半死(网络通但磁盘满)。这才是真实世界——你处理的不是"挂了",而是"半挂了"。
单机故障:
进程 进程
● → X
(全活) (全死)
分布式故障:
N1 N2 N3 N4
● ● ● ● 全活
● X ● ● 一个挂(可能 N1 还以为 N2 活着)
● ▲ ● ● 一个半死(响应慢、超时但不返回错误)
● ● ● ● 全活但 N1-N4 之间网络断了(脑裂)部分失败是分布式最难处理的——因为你没法区分"对方挂了"和"网络通信慢"。这就是后面会反复出现的「network partition vs slow node」问题——同一个超时,可能是对方死了,可能是网延迟,你无法区分。
4.2 Unreliable Network(网络不可靠)
互联网建立在一个事实上:TCP 给你可靠的字节流,但 TCP 之下的物理网络是不可靠的。
- 丢包:路由器拥塞 → 数据包丢 → TCP 重传,看起来"慢"
- 乱序:多路径路由 → 包到达顺序变了 → TCP 重排
- 重复:NAT 设备超时重发 → 同一个包到达两次
- 延迟:跨地域、跨运营商、跨防火墙,延迟不可预测
- 分区:交换机故障、防火墙规则错、BGP 路由抖动 → 整段网络隔离
这一切都是常态,不是异常——AWS / Azure / 阿里云每天都会发生几次"网络抖动",生产系统必须假设网络永远不可靠。详见 02 篇八大谬误。
4.3 Concurrency(并发)
单机并发:加锁就行(互斥量、读写锁、原子操作)。
分布式并发:两个客户端同时往同一个 key 写,谁先谁后?
- 时间戳?两台机器的时钟不同步(详见 05 篇)
- 加锁?怎么锁?在哪台机器上锁?(详见 26 篇分布式锁)
- 仲裁?谁是仲裁者?仲裁者挂了怎么办?(详见 09-15 篇共识)
客户端 A → 节点 1 (写 x=1, t=10:00:00.123)
客户端 B → 节点 2 (写 x=2, t=10:00:00.122)
按时间戳:B 先 (t=10:00:00.122 < t=10:00:00.123)
但节点 2 的时钟比节点 1 慢 100 ms!
真实顺序:A 先
→ 用 wall clock 排序,可能把因果颠倒4.4 No Global Clock(没有全局时钟)
单机系统:有一个唯一的、单调递增的时间——CPU 时钟或操作系统时间。
分布式系统:N 台机器有 N 个时钟,而且都不准。
- NTP 同步精度:几毫秒到几十毫秒,根本不够给数据库做事务排序
- 跨地域 NTP:网络延迟波动大,可能差秒级
- 闰秒、夏令时、时钟漂移:时间甚至会倒流
没有全局时钟意味着:
- 你不能用 wall clock 排序两个事件
- 你不能基于绝对时间设置 TTL("过期"概念变模糊)
- 你不能简单判断"哪个事务先开始"
这就是为什么有 Lamport 时钟、向量时钟、HLC、TrueTime 这一连串发明(详见 05-08 篇)——没有完美的解,只有不同程度的近似。
4.5 Distributed State(状态分散)
单机:一个进程的状态在一个进程里,内存 dump 一下就能看完。
分布式:状态散在 N 台机器,而且每台的副本可能不一样。
- 节点 1 看到的状态:
x=1 - 节点 2 看到的状态:
x=2(还没同步) - 节点 3 看到的状态:
x=3(本地缓存)
没有任何一个时刻,某个观察者能"看到系统的真实状态"——它要么看到旧的(还没同步)、要么看到部分的(只能拿到几个节点的)、要么看到不一致的(节点之间相互冲突)。
这是 CAP / 一致性模型 / 复制协议存在的根本原因——它们都在试图回答"在状态分散的世界里,我能给应用提供什么样的'看起来像单机'的契约"。详见 16-19 篇一致性模型。
五、本系列六层结构对应五座大山
写作计划里讲了本系列六层——这一节快速把"六层"和"五座大山"对应起来,你后续读每一篇都知道它在解决哪类问题。
┌─────────────────────────────────────────────────────────────┐
│ 层级 核心解决的山 关键概念 │
├─────────────────────────────────────────────────────────────┤
│ 心智 + 基础 全貌、词汇、约束 CAP / FLP / 故障模型│
│ (01-04) 全部五座山的入门 八大谬误 │
│ │
│ 时间与因果 山 #4 (无全局时钟) Lamport / HLC │
│ (05-08) Vector Clock │
│ │
│ 共识与复制 山 #1+#2 (部分失败、网络) Paxos / Raft / ZAB │
│ (09-15) 让多机对一个值达成一致 FLP / 拜占庭 │
│ │
│ 一致性模型 山 #5 (状态分散) Linearizability │
│ (16-19) 多副本的"看起来像单机"契约 Causal / CRDT │
│ │
│ 分布式事务 山 #3 (并发) 2PC / Saga / TCC │
│ (20-24) 跨机操作的原子性 Percolator / Spanner│
│ │
│ 工程基石 综合解决五座山的工程零件 Gossip / 选主 │
│ (25-30) 分布式锁 / 服务发现 │
└─────────────────────────────────────────────────────────────┘读者画像里说,这个系列假设你已经用过至少一个分布式中间件——你在 Redis / Kafka / ZK / etcd 上踩过的坑,就是这五座山在你头上砸下来的具体形态。后面 29 篇是把这些坑系统化:告诉你坑长什么样、为什么不可避免、工程上怎么妥协绕过、什么时候妥协会失效。
六、一个简单的"分布式 KV"思想实验
最后用一个思想实验,把这一篇的所有概念串起来。假设你要写一个"分布式 KV 存储"——支持 put(k, v) 和 get(k),数据存在 3 台机器上。听起来很简单,对吧?
6.1 第一版:全写全读
def put(k, v):
for node in [n1, n2, n3]:
node.set(k, v)
def get(k):
return n1.get(k)问题:
put时 n2 挂了,n2 没收到这次写。n1、n3 有新值,n2 有旧值——山 #1 部分失败- 两个客户端并发
put(k, v1)和put(k, v2),n1 收到 v1 在前、n2 收到 v2 在前——最终 n1 是 v1,n2 是 v2,数据分裂——山 #3 并发 + 山 #4 时钟
6.2 第二版:多数派写
def put(k, v):
ok = 0
for node in [n1, n2, n3]:
if node.set(k, v):
ok += 1
return ok >= 2 # 多数派成功
def get(k):
return [n.get(k) for n in [n1, n2, n3]] # 拿三份,看多数问题:
- 客户端 A
put(k, v1),n1+n2 成功,返回 success - 客户端 B 同时
put(k, v2),n2+n3 成功,返回 success - n1=v1, n2=??, n3=v2——n2 看到了两个并发写,谁覆盖谁?山 #3 并发
- 用时间戳?两台客户端时钟不同步——山 #4 时钟
6.3 第三版:加共识协议
引入 Raft——所有写都通过 Leader,Leader 按日志顺序复制到多数派:
def put(k, v):
leader.append_log(SET, k, v) # Raft 复制
# Leader 等待多数派确认后 commit
def get(k):
return leader.read(k) # 强一致读这就是 etcd / Consul / TiKV 的核心——共识协议负责把"分布式状态"伪装成"单机状态"。
但是代价:
- Leader 挂了要选举,选举期间不可用(几秒)——山 #1
- 每次写都要等多数派确认,延迟 = max(到各节点的 RTT)——山 #2
- 跨地域部署时,延迟可能 100 ms+——山 #2
这就是后面 09-15 篇要展开的全部内容——共识协议是"分布式 KV"能正确工作的根基。看完那一层,你再回头看 etcd / ZK / TiKV 的设计,就能直接看穿。
七、几个常被忽略的反直觉事实
7.1 "无状态服务"是营销话术
真正的"无状态"几乎不存在——至少有日志、缓存、配置、连接池这些隐式状态。所谓"无状态服务",只是把状态推给了下游存储。最终某个地方必须有人扛状态,而扛状态那一层才是分布式系统真正难的地方。
7.2 节点越多,系统越脆弱(不是越健壮)
直觉:加机器 = 更可靠。 现实:加机器 = 更多故障点 + 更多协调开销。
节点数 N 越大,任意时刻有节点挂的概率越高——单机 99% 可用,10 个节点全活的概率 ≈ 90%,100 个节点全活的概率 ≈ 37%。分布式系统的"健壮"是靠副本和重试达成的,不是靠"机器多"。
7.3 异步 ≠ 解耦
很多人以为"上了 Kafka 就解耦了"——错。Kafka 只是把"同步调用失败"换成了"异步消息积压",问题没消失,只是变形。消息丢了、消费者挂了、积压爆了——每个都是新坑。
7.4 "我们用的是 X,所以不会有 Y 问题"
最常见的认知错误。Kafka 用了不代表你不会丢消息(配置错了 acks=1 就会丢),ZK 用了不代表你不会脑裂(客户端 session 过期处理不对就会脑裂)。工具不能替代理解——这就是这个系列要写 30 篇的原因。
八、读完这一篇后该有的认知
- 分布式不是"加机器",是"换一种思维方式"——故障模型、时间模型、状态模型全变了
- 能不上分布式就不上——单机 + 主从能撑住 90% 的业务,运维成本是分布式的 1/10
- 真上分布式,准备好为"部分失败 + 网络不可靠 + 并发 + 无全局时钟 + 状态分散"五座大山付代价
- 每个分布式中间件都在解决五座山中的一部分——后续 29 篇会把每个工具拆开看
- 理论不是装饰品——CAP / FLP / Linearizability 不是面试题,是你线上事故复盘时需要的工具
九、本系列的"工程师视角"承诺
本系列不教你:
- 抄写 Paxos / Raft 论文
- 重新实现一个 etcd
- 数学证明(只到关键不变量)
本系列教你:
- 看懂 Kafka / etcd / ZK / TiDB 的设计文档"在抄哪一篇论文"
- 选型时知道"为什么这个场景该选 Cassandra 不该选 ZK"
- 出事故时知道"这个现象是 CAP 还是 FLP 还是 Linearizability 被破坏了"
- 面试白板时讲清楚"为什么 Raft 比 Paxos 火,但 Paxos 不会被淘汰"
理论纵深 + 工程映射——这两个词后续会被反复实践。
十、踩坑提醒
- 以为"分布式 = 加机器"——真正难的是部分失败 + 网络不可靠 + 时钟漂移 + 状态分散
- 业务规模没到就上分布式——10 倍复杂度换 0 倍好处,典型的过度工程
- 以为"无状态服务"就没分布式问题——状态被你推给了下游,问题在下游照样存在
- 以为加节点就更可靠——节点越多故障点越多,冗余靠副本不是靠机器多
- 用 wall clock 给跨机事件排序——两台机器时钟差几十毫秒,因果可能颠倒
- 以为 TCP 可靠就网络可靠——TCP 给你字节流可靠,但连接断了、超时了、重连了,业务层不可靠
- 以为部署到 K8s 就分布式做对了——K8s 解决调度和容器编排,没解决一致性、共识、事务
- 以为分布式锁(Redis)能保证互斥——主从异步复制会丢锁,Redlock 也有争议(详见 26 篇)
- 以为 Kafka 是强一致——它只是"分区内有序 + ISR 同步复制",不是 Linearizability(详见 03 篇)
- 以为读了 DDIA 就懂分布式——DDIA 是地图,你还需要"走过坑"才知道地图哪里是悬崖
下一篇:02-八大谬误与故障模型.md,把 1994 年 Peter Deutsch 提出的"分布式八大谬误"配上 2010-2025 的真实事故复盘——这八条不是说教,是 30 年来全行业的血泪集合。然后引出更深一层:故障模型(Crash-stop / Crash-recovery / Omission / Byzantine)和时间模型(Synchronous / Asynchronous / Partial Synchronous)——这两组词汇是 09 篇 FLP 不可能定理的前置弹药。