# 13 · 规模化的力学:加机器不是免费的 > 一句话点题:**新人以为「扛不住?加机器」是一句结论;架构师知道它是一个开头。** 加机器会在你没想到的地方先断——有状态组件搬不动、热点把一台打爆、尾延迟随扇出放大、协调开销让收益递减。本章承接 [05](05-数据与状态.md) 的「复制/分片/缓存」往下挖:**规模不是把系统放大,而是换一套力学。** 在 AI 几秒就能生成一个能跑原型的今天,**知道「规模会在哪先断」,是架构师最不可替代的判断。** --- ## 一、垂直 vs 水平:加机器到底加在哪 「扛不住了加机器」其实有两条完全不同的路: ``` 垂直扩展(Scale Up) 水平扩展(Scale Out) ────────────────── ────────────────── 把一台机器换得更猛 把很多台普通机器并起来 4 核 → 64 核,16G → 1T 内存 1 台 → 10 台 → 1000 台 ✓ 简单:代码一行不用改 ✓ 理论上无上限、还顺带容错 ✗ 有物理天花板(最猛的机器就那样) ✗ 协调成本:它们要互相通信、同步 ✗ 单点:这台挂了就全挂 ✗ 一致性、分片、再平衡全来了 ✗ 越往上越贵(非线性溢价) ✗ 复杂度从「一台」变成「一群」 ``` > **架构智慧**:垂直扩展是「买时间」——它最简单,在系统还小的时候,**升一台机器永远比改架构便宜**,别急着上分布式。但它有天花板,且不解决单点。**真正的规模化是水平扩展**:用一堆便宜机器顶上,但你为此付出的代价,是从「管一台」变成「管一群」——而本章接下来讲的所有难题,都是这「一群」带来的。 关键的分水岭来自 [05 章](05-数据与状态.md) 的第一性原理:**无状态好扩,有状态难扩。** ``` 无状态组件(Web/API/推理 worker): 有状态组件(数据库/缓存/连接): ┌──┐┌──┐┌──┐┌──┐ ┌──────────────────┐ │实例││实例││实例││实例│ ← 想加随便加 │ 它"记得"东西 │ ← 想加?那两份 └──┘└──┘└──┘└──┘ 流量随便分 │ (余额/库存/会话) │ "记忆"怎么对上? 它们之间不知道彼此 └──────────────────┘ 一加就有同步问题 ``` 所以规模化的第一步,永远是**把状态从计算里挤出去**:让 Web/API 层尽量无状态(可以随便复制),把难搞的状态收拢进少数几个专门管状态的地方(数据库、缓存)精心伺候。**于是「加机器」这件事,在无状态层近乎免费,在有状态层贵得吓人——而你的瓶颈,几乎总是在后者。** > **判断要点**:当有人说「加机器就行」,先问一句:**加的是哪一层?** 无状态层加 10 台是配置改一行;有状态层加 1 台,可能意味着重新分片、搬迁 TB 级数据、几周的迁移窗口。「加机器」从来不是一个均匀的动作。 --- ## 二、分片策略:范围 vs 哈希,以及一致性哈希为什么是神来之笔 [05](05-数据与状态.md) 讲了分片(sharding)是「扩写」的牌,也点了「分片键选错是灾难」。这一节把**怎么分**讲透。两大流派: ``` 范围分片(Range) 哈希分片(Hash) ──────────── ──────────── 按 key 的区间切: 按 hash(key) 切: [A-F]→片1 [G-M]→片2 [N-Z]→片3 hash%N 决定落哪片 ✓ 范围查询高效(扫一段在一个片) ✓ 分布均匀,天然防热点 ✓ 相邻数据物理相邻 ✗ 范围查询废了(相邻 key 散到各片) ✗ 易热点:新数据总往"最后一段"挤 ✗ 加减节点 → 全量重映射(见下) (按时间分片,今天的全压一个片) ``` 范围分片适合「要按区间扫」的场景(时序、按时间翻页);哈希分片适合「均匀打散、随机点查」。但**朴素哈希(`hash(key) % N`)有一个致命缺陷**——它把节点数 `N` 写死进了公式: ``` 原本 4 台:hash(key) % 4 加到 5 台:hash(key) % 5 ──▶ 几乎每一个 key 的归属都变了!N 一变,余数全乱。 结果:加一台机器,要搬迁 ~80% 的数据。整个集群瞬间被"再平衡"流量打瘫。 ``` **一致性哈希(Consistent Hashing)** 就是来解这个的。它的核心思想美得像魔术:**把哈希空间想象成一个环(0 到 2³²-1 首尾相接),节点和数据都哈希到环上;一个 key 顺时针找到的第一个节点,就归它。** ``` 0/2³² │ 节点D │ 节点A • 数据 k 哈希落在这,顺时针 ● │ ● 找到的第一个节点是 A → 归 A │ ╱ k(数据) ───────────┼─────────── │ ╲ ● │ ● ╲ 现在加入节点 E(落在 A 和 B 之间) 节点C │ 节点B ──▶ 只有"原本归 B、但现在落在 E 之前"的那一小段 │ 数据需要搬到 E。其余所有节点纹丝不动! ``` > **架构智慧**:一致性哈希的全部价值,就一句话——**加/减一个节点,平均只需搬动 `1/N` 的数据(只影响环上相邻的那一段),而不是朴素哈希的几乎全量。** 这是从「加机器=全集群地震」到「加机器=局部微调」的质变,也是几乎所有大规模分布式存储(Cassandra、DynamoDB、ScyllaDB)能平滑扩容的地基。 但朴素的一致性哈希还有个问题:**节点在环上的位置是随机的,可能分布不均**(有的节点管了一大段、有的只管一小段),而且某节点一挂,它的全部负载会压给顺时针的下一个邻居。解法是**虚拟节点(virtual nodes / vnodes)**: ``` 每个物理节点,在环上放很多个"分身"(比如 256 个虚拟节点): 物理节点 A ──▶ 散布成 A₁ A₂ A₃ ... A₂₅₆ 遍布整个环 • 分布更均匀:大数定律抹平了随机性 • 一台挂了,它的负载被"打散"分给所有其他节点,而不是全压给一个邻居 • 异构机器好处理:猛的机器多放几个 vnode,弱的少放 ``` > **判断要点**:范围 vs 哈希不是「哪个更好」,是「你的查询形态是什么」——要按区间扫(时序、翻页)用范围;要均匀打散用哈希。而一旦选了哈希、且需要弹性扩容,**一致性哈希 + 虚拟节点几乎是标配**。这正是 [05](05-数据与状态.md) 里「再分片很痛」那句话的正解。 --- ## 三、热点:一个 key 就能把整台机器打爆 分片把负载摊到 N 台,**前提是负载真的均匀**。现实里,负载从来不均匀——它遵循幂律:少数 key 占据绝大多数流量。这就是**热点(hot key / hot partition)**: ``` 理想(均匀):每片 10% 流量 现实(幂律):一个 key 占 50% ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ████████████ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ ▓ 分片再多也没用——那个热 key 所在的 ↑ 顶流明星发帖 / 秒杀那一件商品 / 单台机器被打爆,其余的全在睡觉 突发新闻 / 群里 @everyone ``` 热点的可怕在于:**分片本来是来救你的,但热点让分片失效**——你加再多机器,流量还是全压在持有那个热 key 的**那一台**上。识别与打散的手段: | 手段 | 怎么做 | 适合 | | -------------------- | --------------------------------------------- | ------------ | | **加盐(salting)** | 把热 key 拆成 `key#1` `key#2`…`key#N`,人为散到多片;读时聚合 | 写热点(如计数器) | | **本地缓存** | 在应用进程内存里缓一份,大部分读根本不出本机 | 读热点(配置、热门内容) | | **只读副本复制** | 给热点数据多做几个只读副本,把读分摊出去 | 读多写少的热点 | | **请求合并(coalescing)** | 同一瞬间对同一 key 的 N 个请求,只放一个去查后端,其余等结果 | 读击穿(见第四节) | > **架构智慧**:**热点是规模化里最反直觉的杀手**——它让「我已经分片了/加机器了」的安全感瞬间破灭。识别热点要靠监控(按 key 维度的流量分布,而不只看总量);打散的核心思路只有一个——**把"一个点"变成"一片"**:要么把热 key 物理拆开(加盐),要么把它复制到多处(本地缓存/只读副本),要么把重复的请求归一(合并)。下面的 Twitter 和 Discord 案例,就是这两招的真实战场。 --- ## 四、多级缓存与缓存踩踏:墙塌的那一刻 [05](05-数据与状态.md) 讲了单层缓存的三大坑(穿透/击穿/雪崩)。真实的大规模系统是**多级缓存**——数据被复制到一层又一层,离用户越近越快: ``` 用户 ──▶ ┌─────┐ ──▶ ┌──────┐ ──▶ ┌───────┐ ──▶ ┌──────┐ ──▶ ┌─────┐ │ CDN │ │ 边缘 │ │ 应用本地│ │分布式 │ │ DB │ │(静态)│ │(区域)│ │ 缓存 │ │缓存 │ │(真相)│ └─────┘ └──────┘ └───────┘ │Redis │ └─────┘ ~10ms ~20ms <1ms(进程内) └──────┘ ~10ms+ 离用户最近、命中最快 ~1ms 每一层都挡掉一批流量,真正落到 DB 的只剩极少 ``` 层级越多,DB 越轻松——但**每多一层副本,一致性就多一道难题**(改了 DB,这五层怎么一致地失效?)。而最凶险的是**缓存踩踏(thundering herd / cache stampede)**: ``` 一个超热的 key,缓存里它过期的那一瞬间: 时刻 T: 缓存有值 ──▶ 1 万 QPS 全部命中,DB 毫无压力 时刻 T+ε: 这个 key 过期 ──▶ 1 万个请求同时发现"没了" ──▶ 1 万个请求"一起"涌向 DB 去重建同一个值 ──▶ DB 瞬间被同一个查询打 1 万次 ──▶ 雪崩,可能引发连锁宕机 ``` 它比 [05](05-数据与状态.md) 的「击穿」更普遍也更隐蔽——**不是缓存坏了,而是缓存「正常过期」这件事本身,在高并发下成了同步的洪峰**。三道防线: - **请求合并(single-flight)**:同一 key 的并发重建,只放第一个去查 DB,其余的订阅它的结果。**1 万次查询塌缩成 1 次。**(这正是 Discord 的解法) - **过期时间加随机抖动**:别让一批 key 在同一秒集体过期(防雪崩)。 - **逻辑过期 / 提前异步刷新**:key 不真的删,后台在它快过期时悄悄刷新,读端永远命中。 > **判断要点**:多级缓存是规模化的标配,但它把「一份数据」变成了「五份副本」——**你享受的每一点速度,都在一致性账本上记了一笔。** 而缓存踩踏提醒你:在大规模下,**连「正常过期」这种例行操作都可能成为同步的灾难**。好的缓存设计,一半是命中率,另一半是「失效时别让所有人同时扑向 DB」。 --- ## 五、多区域 / 多活:把系统铺到地球两端 当用户遍布全球、或要容忍整个机房 / 区域挂掉时,你要把系统部署到**多个地理区域**。最简单的是「一主多备(一个区域写,其余只读 / 待命)」;最难、也最强的是**多区域多活(multi-region active-active)**——每个区域都能读也能写: ``` ┌─── 美国区 ───┐ 跨区 RTT ~70-150ms ┌─── 欧洲区 ───┐ │ App + DB副本 │ ◀═══════════════════════════════▶ │ App + DB副本 │ │ 就近服务美国 │ (光速 + 距离的硬下限) │ 就近服务欧洲 │ └──────────────┘ └──────────────┘ ▲ ▲ 美国用户(低延迟) 欧洲用户(低延迟) ★ 难点:同一条数据,美国和欧洲"同时"被改了,听谁的?(写冲突) ``` 多活买到的是**数据本地性(就近读写、延迟低)+ 区域级容灾(一个区域全挂,流量切到另一个)**。但它撞上了 [10 章](10-分布式系统的硬道理.md) 的全部硬道理: - **跨区延迟是硬约束**:北京到弗吉尼亚一个来回 ~150ms。如果每次写都要等另一个区域确认(强一致),用户就要为光速买单。 - **写冲突必须有解**:两个区域同时改同一条数据,合并时听谁的?这正是 [10](10-分布式系统的硬道理.md) 因果/并发的实战: ``` 美国区:把昵称改成 "Alice" ┐ ├─ 几乎同一时刻、跨区还没同步 ──▶ 冲突! 欧洲区:把昵称改成 "Alicia" ┘ ─ LWW(最后写入者赢):按时间戳留一个 ── 简单,但另一个"丢更新"了 ─ CRDT(无冲突数据类型):数学上保证任意顺序合并都收敛到同一结果 (协同文档、计数器、购物车常用)── 强,但建模受限 ─ 业务拆分:让两区天然改不同字段/不同主键 ── 从源头让冲突不可能 ``` - **就近路由**:用 GeoDNS / Anycast 把用户引到最近的区域,这是多活省延迟的前提。 > **架构智慧**:多活不是「把系统复制两份」那么简单,它在 [10](10-分布式系统的硬道理.md) 的 PACELC 账单上做了一个昂贵选择——**为了低延迟和高可用(就近 + 容灾),它几乎必然放松强一致(允许跨区短暂不一致 + 用冲突解决兜底)。** 绝大多数业务**不需要**全球多活;先问自己「我的用户真的分布在多大洲、我真的需要扛整个区域级故障吗」,再决定要不要付这笔极重的复杂度账。 --- ## 六、尾延迟的数学:为什么 p99 才是真实体验,扇出会放大它 新人看延迟只看平均值;架构师只看**尾部(p99、p999)**。为什么? ``` 平均延迟 50ms 听起来很美,但分布可能是: 99% 的请求:20ms(快) 1% 的请求:2000ms(慢得离谱) 平均 ≈ 40ms ✓ "达标" ↑ 但每 100 个用户就有 1 个在转圈 2 秒 ── 平均数把"少数极慢"稀释没了,可那 1% 恰恰是你最该在意的体验。 ``` **真正的杀手是扇出放大(fan-out amplification)。** 现代系统里,一个用户请求往往要扇出成几十上百个内部子调用(查 100 个分片、调 100 个微服务),**只有等最慢的那个子调用回来,整个请求才算完成**: ``` 一个请求扇出到 100 个子调用,每个子调用 p99 = 1%(即 1% 概率慢): 整个请求要"全部"子调用都快,才算快。 全部快的概率 = (99%)¹⁰⁰ ≈ 36.6% ──▶ 也就是说:有 63% 的概率,这个请求会撞上"至少一个慢子调用"而变慢! 单个子调用只是 p99 慢,整体请求却几乎"必然"慢 ── 这就是放大。 ``` > **架构智慧(本章最反直觉的一条)**:**在大扇出系统里,整体的 p99 趋近于子调用的 p999 甚至 p9999。** 子组件「偶尔慢一下(p99)」无所谓——这个朴素直觉**在扇出面前是错的**。一个慢节点,会被 100 倍的扇出放大成「几乎每个请求都慢」。这就是为什么 Google 的工程师说:**在规模化系统里,治理尾延迟比优化平均延迟重要一个数量级。** 对冲请求(hedged requests)是治尾延迟的利器:**同一个请求发给两个副本,谁先回用谁。** 但全发两份会让负载翻倍,所以更聪明的做法是 Google 的「延迟对冲」—— ``` 先发给副本 A;若 A 在"p95 时间"内没回,才补发给副本 B,用先回的那个。 ──▶ 只对最慢的那 5% 请求才发第二份 ──▶ 额外负载仅 ~5% 却能把长尾砍掉一大截(见下方 Google 真实数据)。 ``` > **判断要点**:延迟是分布,不是数字。**对外承诺和内部 SLO 都要用 p99/p999,而不是平均。** 越是高扇出的系统([搜索引擎](../templates/search-engine/README.md) 一次查询扇出到成百上千个索引分片、[社交信息流](../templates/social-feed/README.md) 组装一屏要拉很多源),越要把尾延迟当头等大事——对冲请求、超时预算、慢副本剔除,都是为它准备的武器。 --- ## 七、排队的直觉:为什么逼近 100% 利用率时,系统会突然爆炸 最后一个、也是最深的力学——**排队论**。它解释了一个让无数人栽跟头的现象:系统在 70% 利用率时一切正常,加一点流量到 95%,延迟突然飙升十倍。 先记住 **Little 定律**(简洁到不像真的,却永远成立): ``` L = λ × W 队列里平均的请求数 = 到达率 × 每个请求平均停留时间 ── 它的用处:你能不能从"并发数/吞吐"反推出延迟,或反过来估容量。 ``` 而最该刻进骨子里的,是**利用率与排队长度的关系**(M/M/1 直觉): ``` 平均排队时间 ∝ 1 / (1 − ρ) (ρ = 利用率) ρ=50% → 因子 2 延迟还好 ρ=80% → 因子 5 开始肉眼可见地变慢 ρ=90% → 因子 10 明显卡顿 ρ=95% → 因子 20 开始排长队 ρ=99% → 因子 100 ★ 雪崩:延迟爆炸式飙升 ★ 延迟 │ ╱← 逼近 100% 时 │ ╱ 曲线near-垂直拉起 │ ___╱ │ ________────── │_____───────── └──────────────────────────────────▶ 利用率 ρ 0% 50% 80% 90% 95% 99% 100% ``` 为什么?**因为请求到达是随机的(有波峰波谷),而高利用率下系统没有任何余量去消化突发的波峰**——波峰一来,请求开始排队,队列里的请求又拖慢后面的,正反馈。利用率越接近 100%,这个正反馈越剧烈,直到延迟趋于无穷。 > **架构智慧**:**「留余量」是设计,不是浪费。** 那个看起来「闲着 30% 没跑满」的服务器,买的是**应对突发的能力**和**可控的尾延迟**——把利用率压到 100% 省下来的机器钱,会在第一波流量尖峰里以「系统雪崩」的形式十倍奉还。这也是为什么 [在线票务](../templates/online-ticketing/README.md) 这种「开售即洪峰」的系统,必须按峰值而非均值预留容量。 而当你想靠加机器来提升容量时,还有最后一个冷水——**通用可扩展性定律(USL)与 Amdahl 定律**: ``` 理想线性: 10 台 = 10 倍 实际: ↑加速比 ↑加速比 │ ╱ 理想(线性) │ ╱‾‾‾╲___ ★ 加到一定数量后 │ ╱ │ ╱ 反而下降! │ ╱ │ ╱ 实际(USL) │ ╱ │╱ └──────────▶ 机器数 └──────────▶ 机器数 • Amdahl:串行部分(必须排队的那点)限制了上限 • USL 更狠:节点间"协调开销(coherency)"随规模增长 ──▶ 加机器收益递减,甚至变负 ``` > **判断要点(收束全章)**:**加机器的收益不是线性的,甚至可能变负。** 机器越多,它们之间「对齐状态、互相协调」的开销越大(这正是第一节「管一群」的代价、[10 章](10-分布式系统的硬道理.md) 共识与协调的成本)。这就是为什么「无状态、少协调」的设计能近线性扩展,而「重协调、强一致」的设计加到一定规模就撞墙。**架构师的活,就是尽量减少需要协调的部分**——好的规模化设计,本质是「让加进来的机器尽量不需要互相说话」。 --- ## 📌 真实案例:三个把本章力学顶到极限的大型系统 **① Discord:用一致性哈希 + 请求合并,扛住「@everyone」的热点(第二、三、四节)** Discord 存了**万亿级**消息,按 `(channel_id, bucket时间窗)` 分片。问题来了:**一个大服务器里有人 @everyone 发公告,那一个分片瞬间被海量并发读打爆**——这就是教科书级的「热点分区(hot partition)」。Discord 的两招正是本章第三、四节的实战:**① 请求合并(request coalescing)**——同一瞬间对同一行的成千上万个读,只放第一个去查数据库,其余的订阅它的结果,**「上万次查询塌缩成一次」**;**② 一致性哈希路由**——同一个 channel 的所有请求,都被路由到同一个数据服务实例,合并才有意义。后来又从 Cassandra 迁到 **ScyllaDB**(shard-per-core 架构,工作负载隔离更强,防止单个热分区拖垮整个节点):集群从 **177 个 Cassandra 节点缩到 72 个 ScyllaDB 节点**,**读 p99 从 40–125ms 降到 15ms,写 p99 从 5–70ms 降到 5ms**。迁移用 Rust 重写后,从预估 3 个月压到 **9 天**(峰值 320 万条/秒)。 > 📎 [Discord 官方工程博客:How Discord Stores Trillions of Messages](https://discord.com/blog/how-discord-stores-trillions-of-messages) **② Twitter / X:名人 fan-out 与「Justin Bieber 问题」(第三节热点)** Twitter 的时间线靠**写时扇出(fan-out on write)**——你发一条推,系统把它"推"进所有粉丝的时间线缓存。这对普通人很高效,但碰上**几千万粉丝的顶流**就成了灾难:发一条推 = 几千万次写入,这就是著名的**「Justin Bieber 问题」**——它不是某个明星的事,而是**幂律热点的代名词**(早期 Bieber 的海量粉丝甚至能把他「关注数」那行的 MySQL 行锁压出错误率飙升)。解法是**混合(hybrid)策略**:普通用户走写扇出(发文时预算好);**少数超级大 V 走读时拉取**——不预先推给所有粉丝,而是你刷新时现去拉他们的推、再和你的时间线合并。**对热点,把"写时打散到千万处"换成"读时从一处拉取"。** > 📎 [The Tail at Scale (Dean & Barroso, CACM 2013)](https://www.barroso.org/publications/TheTailAtScale.pdf) 同样指出,Twitter 这类高扇出服务的尾延迟治理是规模化的核心命题之一。 **③ Google《The Tail at Scale》:尾延迟与对冲请求的奠基(第六节)** 这篇 Jeff Dean 与 Luiz Barroso 2013 年发表于 CACM 的论文,是**尾延迟工程的圣经**。它点破:在动辄扇出到上千个叶子节点的系统里,**单个组件「偶尔慢一下」会被扇出放大成「整体几乎必然慢」**,所以治理 p99/p999 至关重要。它给出的**延迟对冲(hedged requests)**实测数据极有说服力:**等第一个请求超过 p95 才补发第二份,只增加约 2–5% 的额外负载,却能把 1000 个取值的 p999 从 1800ms 砍到 74ms。** 这正是第六节的理论原型。 > 📎 [The Tail at Scale 论文(PDF)](https://www.barroso.org/publications/TheTailAtScale.pdf) | [CACM 版本](https://cacm.acm.org/research/the-tail-at-scale/) **④ 一致性哈希的起源(第二节)**:这个让大规模存储平滑扩容的核心思想,来自 **David Karger 等人 1997 年的论文**《Consistent Hashing and Random Trees》——最初是为「缓解万维网热点(relieving hot spots on the Web)」而生,正好对应本章「分片 + 热点」两大主题。次年(1998),论文作者 Lewin 与 Leighton 创办了 **Akamai**,把它变成了今天全球 CDN 的基石。而 Amazon **Dynamo**(承接 [05](05-数据与状态.md))则把一致性哈希 + 虚拟节点用进了生产级 KV 存储,成为 Cassandra / DynamoDB / ScyllaDB 这一脉的源头。 > 📎 [Consistent Hashing and Random Trees (Karger et al., STOC 1997)](https://dl.acm.org/doi/10.1145/258533.258660) --- ## 🤖 AI / vibe coding 视角:原型在规模面前最先崩的地方 LLM 推理服务的扩展,本身就是本章力学的**活案例**: - **尾延迟敏感 + 扇出**:[模型推理服务](../templates/inference-serving/README.md) 的 **TTFT(首字延迟)和 TPOT(每字延迟)** 本质就是 p99 体验;而一个 **agent 把任务拆成 N 个并发子调用**(同时查多个工具、跑多个子代理),就是第六节的**扇出放大**——只要有一个子调用撞上长尾,整个 agent 的这一步就被拖慢。子任务越多,整体尾延迟越逼近单个子调用的 p999。 - **热点 + 排队**:推理服务的**连续批处理**正是在和第七节的排队论搏斗——GPU 利用率拉得越满,吞吐越高,但单请求的尾延迟(排队等批)也越高;把利用率逼到 100% 同样会让尾延迟爆炸。而一个爆火的应用,会让某个模型 / 某个 prompt 前缀成为热点。 - **召回-延迟权衡**:[向量数据库](../templates/vector-database/README.md) 的 ANN「用一点精度换巨大速度」,和本章「用一致性 / 精确性换规模」是同一种交易——规模化处处是这种「放弃一点完美换数量级提升」的取舍。 > **架构智慧**:vibe coding「先跑起来」的原型,在 demo 和小流量下完美无瑕——因为它从没遇到过热点、扇出、和逼近满载的排队。**但它会在真实规模面前最先崩,而且崩在你最意想不到的地方**:不是那个最复杂的模块,而是那个被名人 / 爆款打成热点的单一分片,那个利用率悄悄爬到 95% 的队列,那个被 100 倍扇出放大的慢副本。AI 能瞬间生成「能跑」的代码,但它**不知道你的流量长什么样、热点会出现在哪、你愿意为尾延迟留多少余量**——这些都是 [质量属性与取舍](06-质量属性与取舍.md) 意义上的、依赖业务判断的取舍。**预判「规模会在哪先断」,是 AI 给不了、只能由架构师下的判断。** --- ## 🎯 随堂检验 --- ## 本章小结 - **核心论断**:**加机器不是免费的,而且不是均匀的。** 无状态层近乎免费,有状态层贵得吓人;而规模化不是把系统放大,是换一套力学——热点、尾延迟、排队、协调开销,会在你没想到的地方先断。 - **垂直 vs 水平**:垂直扩展简单但有天花板、不解决单点;水平扩展才是真规模化,代价是从「管一台」变成「管一群」。第一步永远是把状态从计算里挤出去。 - **分片与一致性哈希**:范围分片利于区间扫但易热点;哈希分片均匀但范围查询废。朴素哈希一加节点就全量重映射;**一致性哈希让加/减节点只搬 1/N 数据**,虚拟节点再抹平不均与故障冲击——这是大规模存储平滑扩容的地基。 - **热点**:负载遵循幂律,一个热 key 能让分片失效、打爆单台。打散的唯一思路是「把一个点变成一片」:加盐拆分、本地缓存、只读副本、请求合并。 - **多级缓存**:CDN→边缘→应用→分布式缓存→DB,层层挡流量,但每层副本都加一道一致性债;**缓存踩踏**让「正常过期」都能成洪峰,用 single-flight、随机抖动、提前刷新来防。 - **多区域多活**:买到本地性与容灾,代价是撞上跨区延迟、写冲突(LWW/CRDT/字段拆分)、就近路由——在 PACELC 上几乎必然放松强一致。多数业务不需要,先问清楚再付账。 - **尾延迟的数学**:延迟是分布不是数字,p99/p999 才是真实体验;**扇出放大**让整体 p99 趋近子调用的 p999——对冲请求(等 p95 再补发)只加 ~5% 负载就能砍掉长尾。 - **排队的直觉**:Little 定律(L=λW);利用率逼近 100% 时排队长度按 1/(1−ρ) 爆炸式增长——「留余量」是设计而非浪费。USL/Amdahl:协调开销让加机器收益递减甚至变负,**好的规模化设计让机器尽量不需要互相说话**。 - **AI 时代主线**:LLM 推理(尾延迟、连续批处理排队)、agent 并发子任务(扇出放大)、向量库(精度换速度)处处是本章力学;vibe coding 原型在真实规模 / 热点 / 尾延迟面前最先崩——**预判「规模会在哪先断」是架构师不可替代的判断**。 > **承上启下**:这一章讲的是「同一个系统怎么变大(scale up/out)」——加机器、分片、缓存、治尾延迟。但有一类规模问题,加机器解决不了:当系统**功能太多、团队太大、一个代码库谁都不敢动**时,你要做的不是「加机器」,而是「**拆系统**」。下一章 [14 · 演进与拆分大型系统](14-演进与拆分大型系统.md),我们从「机器的规模化」走向「系统与组织的规模化」:单体何时该拆、怎么沿着业务边界拆、绞杀者模式如何安全演进,以及为什么「康威定律」决定了你的架构终将长成你组织的样子。