fedorthinks

速成课 · No. 09

扩展不是让一切都变快——而是找出在负载下最先撑不住的那一处,把它拓宽,然后是下一处。大多数系统需要这么做的远比建造者担心的要少;真正的本事,是知道该伸手去拿什么、又在什么时候去拿。

21 个概念 · 每个配一幅图 · 末尾有速查表

§ 01

扩展不是你一按就灵的某个开关——它是一项持续的活儿:扛住更多负载而不倒下。它从一个想法起步:找出最先崩掉的那一处。

Scalability — 扛住更多而不崩

一家小咖啡馆运转良好,直到一辆旅游大巴停了下来——突然之间,那一个咖啡师、那一台收银机、那几张桌子都跟不上了。扩展就是为这辆大巴做准备。

Scalability(可扩展性)是系统应对增长的能力——更多用户、更多请求、更多数据——同时保持快速、保持在线。它不是比谁最聪明;而是在负载成倍增加时,系统不会撑不住。

latencyload: users · requests · datascales: stays fastbuckles: one part can't keep up
负载在增长。可扩展的系统依然很快;不能扩展的系统会在某个时刻突然崩掉。
ConceptScalability· 应对增长
是什么
在不变慢、不宕机的前提下,承受更多负载的能力。
如何运作
几乎所有扩展问题都归结为一件事:某个地方成了跟不上的那一环。
例子
一辆旅游大巴刚刚停在门口的那家咖啡馆。
关联

靠 Vertical Scaling 和 Horizontal Scaling 增长;会撞上 Bottleneck。

Vertical vs Horizontal — 向上还是向外

想多运货,你可以买一辆更大的卡车——但大也有个限度——或者买更多卡车:几乎没有上限,但现在你需要一个调度员。

增加容量恰好有两种办法。**Vertical scaling(scale up,纵向扩展)**是换一台更大的机器——更多 CPU、更多内存。简单,但有一个硬性的天花板,而且仍然是单点故障。**Horizontal scaling(scale out,横向扩展)**是让更多机器一起干活——几乎没有上限,但需要协调(负载均衡、共享状态)。垂直扩展为你争取时间;**真正的规模在水平扩展里。**细节见第 2 章。

Vertical — scale uphard ceiling4 CPU64 CPUbigger boxeasy · single point of failureHorizontal — scale outLoad balancerappappappappno real ceiling · needs coordination
左——一台机器变大,撞上天花板。右——机器变多,前面放着一个 load balancer。

Bottleneck — 最窄的那一点

一条公路的速度取决于它最窄的那一段——把其他每条车道都拓宽也没用,除非你先解决那个卡口。

Bottleneck(瓶颈)是系统里最慢的那一环,它限制着其他一切。系统的速度只和那一环一样快。把资源砸在错的地方,就是白白烧钱;要做的是找出真正的瓶颈——数据库、某个慢服务、网络——把它拓宽,然后找下一个。

the bottleneckCDN10k rpsApp servers5k rpsDatabase800 rpsEmail API3k rpsthe whole system = its slowest part = 800 rpswiden it → the next bottleneck shows up
一条链子能承受多少,取决于它最弱的那一环。这里就是数据库。
ConceptBottleneck· 卡口
是什么
那个跟不上、拖慢整个流程的环节。
如何运作

拓宽一个瓶颈,下一个就会冒出来。扩展是一连串的瓶颈,不是一次性的修复。

例子

CDN 和应用服务器每秒能处理几千个请求,数据库只能处理八百个。再加服务器也无济于事。

陷阱
人们很容易去"修"那些只是感觉慢的地方,而不是真正慢的地方。
关联
用 Measure First 找出来;用 Scaling Ladder 的各级台阶来处理。

Premature Scaling — 为你还没有的负载提前建设

你不会为通往林中小屋的路修一条六车道的高速公路。一条好路就能载下所有会来的人。

Premature scaling(过早扩展)就是为你并没有的负载去建设。一台像样的服务器加一个数据库,能服务的用户数多得惊人。在还没有负载之前就上 sharding、微服务、复杂的缓存——这是你不需要的成本和复杂度。

your real load1 server + DBholds it easilybuilt for millionscost + complexity you don't need
你的真实负载、一台服务器加一个数据库能扛住的量,以及“为了未来”建出来的东西。
Anti-patternPremature Scaling· 扩展过早
问题
复杂度和金钱花在了并不存在、也许永远不会来的负载上。
例子
给一个只有一百个用户的应用上分片数据库和微服务。
补救
先测量;在真正疼的时候,去扩展真正疼的那一处。
关联
解药是 Measure First 和 Scaling Ladder。

全景图:七组共 21 个概念

一张地铁线路图:先看到整条线路,再一站一站地坐过去。

课程的每一章就是一组概念。它们大致按一个不断成长的系统遇到它们的顺序排列:先是基础,然后是更多服务器、分散流量、缓存、数据库、队列——最后是头脑清醒地运用这一切的规则。

FoundationsScalabilityBottleneckPremature scalingScale outVertical scalingHorizontal scalingStateless serverSpread trafficLoad balancerHealth checkRolling deployDo less workCacheCDNCache invalidationScale the dataDatabase bottleneckRead replicaShardingAsync & decoupleBackground jobLoad levelingDecouplingScale sanelyMeasure firstScaling ladderDesign for failure
七组概念——第 1–7 章依次对应。

如何阅读每个概念

一本护照:每个人的栏目都一样,所以很容易比较。

从这里开始,每个概念都有相同的几个部分:英文名称(记住它——这是你在文档和面试里会遇到的术语)、一幅生活中的画面、一张图和一张卡片:问题、思路、例子、代价、何时使用,以及它与其他概念的关联。最后有一份速查表,把所有术语放在一页上。

扩展不是让一切都变快。而是找到瓶颈,把它拓宽,然后重复。

§ 02

沿着扩展阶梯迈出的头几步:先把一台大机器用到头,再让应用能跑在任意多台相同的机器上。

Vertical Scaling — 先换台更大的机器

厨房忙不过来时,第一个办法是换个更大的灶台、加更多操作台——而不是开第二家餐厅。

**Vertical scaling(scale up)**是最简单的一招:一台更大的机器。不用改代码,很快就能换来实实在在的余量——往往够用很长时间。但它有一个硬性的天花板(你买不到无限大的服务器),而且这一台机器仍然是单点故障。

hard ceiling: no infinite server2 CPU4 GB8 CPU32 GB64 CPU512 GBstill one box =single pointof failureno code changes · buys time fast
每一级都是更强的机器。但天花板很近,而且它仍然只是一台机器。
PracticeVertical Scaling· scale up
问题
服务器扛不住负载:CPU 或内存不够。
思路
换一台 CPU 和内存更多的机器。
例子
数据库从 8 核服务器换成 64 核的。
代价
有硬性的天花板;一台机器 = 一个单点故障。
何时使用
最先用。用它来争取时间,而不是当作终点。
关联
Scaling Ladder 的第一级台阶;下一步是 Horizontal Scaling。

Horizontal Scaling — 许多台机器,干同一件事

一个收银台变成十个——现在十位顾客可以同时被服务,而且如果有一台卡住了,其余九台照常运转。

Horizontal scaling(scale out)意思是运行应用的许多个副本,把工作分摊到它们身上。它几乎没有上限,而且能扛住任何一台机器的损失。难点在协调:得有东西来分发请求(第 3 章),而且各个副本不能各自持有私有状态——这就是下一个关键想法。

Load balancerapp #1same codeapp #2same codeapp #3downapp #4same codeapp #5same codemany identical copies · lose one, the others carry on
五个相同的副本放在 load balancer 后面。一个挂了——另外四个照常工作。
PracticeHorizontal Scaling· scale out
问题
一台机器,哪怕是最大的,也会撞上天花板。
思路
运行 N 个相同的应用副本,把请求分摊给它们。
例子
黑色星期五,一家商店运行 20 个副本,而不是平时的 4 个。
代价
需要协调:一个 load balancer,和一个放状态的共享位置。
何时使用
垂直扩展不够用,或者需要扛住机器宕机的时候。
关联
只有配合 Stateless Server 才行得通;需要 Load Balancer。

Stateless Server — 任何一台机器都能处理任何请求

一个呼叫中心,任何一位坐席都能接任何一通电话,因为客户的所有资料都存在共享系统里——而不是在某一位坐席的脑子里。

要让水平扩展奏效,应用服务器必须是**无状态(stateless)**的:在请求之间,它们不在自己的内存里保留任何关于用户的东西。会话数据、上传的文件、进度——全都存在某个共享的地方(数据库、缓存、客户端携带的 token)。这样任何请求都可以落到任何一台服务器上,增加或失去一台机器都不会有什么改变。

Requestapp serverremembers nothingapp serverremembers nothingapp serverremembers nothingany one of themShared statedatabase · cache · client token
请求可以落到任何一台服务器上:服务器什么都不记,所有状态都放在共享存储里。
ConceptStateless Server· 无本地状态
问题
服务器把会话存在自己的内存里——用户被"钉"在它身上,一旦崩溃,一切都丢了。
思路
服务器在请求之间什么都不保留;状态存在数据库、缓存或客户端的 token 里。
例子
购物车存在 Redis 里,而不是进程内存里;登录就是一个 token。
代价
每个请求都要从共享存储里取回自己的状态。
何时使用

只要你打算用不止一台服务器,就始终用。正是无状态,才让"多加几台服务器"真正行得通。

关联
是 Horizontal Scaling 和 Rolling Deploy 的前提。

垂直扩展买来时间;水平扩展买来规模。而水平扩展只有在你的服务器自己什么都不记的时候才行得通。

§ 03

一旦有了许多台服务器,就得有东西站在前面,决定每个请求由谁处理。这就是 load balancer——扩展后系统里的交通警察。

Load Balancer — 一个前门,许多服务器

银行里的引导员,把每位顾客指给任何一个空闲的柜员——这样就不会有哪个柜员被淹没,而其他柜员闲着。

Load balancer(负载均衡器)——Nginx、HAProxy、云厂商的均衡器——是唯一的入口,它按简单的规则把进来的请求分给你的服务器池:round-robin(轮询),或者发给最闲的那台(least connections,最少连接)。客户端找的是它,而不是某一台具体的服务器,所以你可以在它后面悄无声息地增减机器。

clientclientclientLoad balancerNginx · HAProxy · cloudserver 1server 2server 3server 4one address for clients · round-robin or least-busy
客户端只知道一个地址。Load balancer 把它们的请求分给各台服务器。
ToolLoad Balancer· LB
问题
有许多台服务器——谁来决定每个请求去哪里?
思路
一个入口把请求分给一池服务器。
例子
Nginx 放在某个 API 的四个副本前面,按 round-robin 分发请求。
代价
多了一个组件,而它自己不能变成单点故障。
何时使用
只要你有不止一台服务器。
关联
**它把一堆服务器变成一个系统。**使用 Health Checks。

Health Check — 别再往死掉的机器上发请求

引导员发现某个柜员离开了,就干脆不再把人派到那个窗口——没人会在空桌子前干等。

有了 health check(健康检查),load balancer 会不断检查哪些服务器是健康的,并绕开不健康的那些。宕机或过载的机器会被自动摘出轮转,流量流向其余机器。扩展后的系统就是这样在一台机器挂掉时,让用户毫无察觉的。

Load balancerserver A200 OKserver Bno responseserver C200 OKGET /health ✗pulled from rotationtraffic only goes to servers that answer the check
服务器 B 没有回应检查——load balancer 不再给它发流量。
PracticeHealth Check· liveness probe
问题
Load balancer 还在往一台已经死掉的服务器发请求。
思路
定期探测每台服务器;没有回应——摘出轮转;恢复了——放回去。
例子
每隔几秒 GET /health;没有 200——这台服务器被摘掉。
代价
写得不好的检查会撒谎:数据库已经连不上了,它还说"健康"。
关联

Load Balancer 的一部分;是 failover 和 Design for Failure 的基础。

Rolling Deploy 与 Failover — 白捡的好处

你可以一次翻修一条收银通道,而其他通道继续营业——商店从不关门。

一旦流量通过均衡器流向可互换的服务器,一些强大的能力几乎是白送的:零停机(滚动)部署——每次更新几台服务器;failover(故障切换)——一台挂了,流量转移到其余的;还有快速的回滚。均衡器加上无状态服务器,是让系统既可扩展又有韧性的骨架。

Load balancerv1servingv1servingv2deploying…v2servingupdate a few servers at a time; traffic routes around thema server dies → failover · bad release → route back to v1
服务器一次更新一台,其余的继续为用户服务。
PracticeRolling Deploy· 零停机部署 + failover
问题
发布新版本就意味着"网站停机"。
思路
每次更新几台服务器,其余的继续扛流量。
例子
四台服务器里,两台已经是 v2,一台正在更新,一台还在 v1——用户毫无察觉。
代价
有一段时间两个版本同时运行——它们得能和平共处。
关联
需要 Stateless Server、Load Balancer 和 Health Checks。

Load balancer 把许多台服务器变成一个地址——也把一台机器的死亡变成一件无关紧要的事。

§ 04

最快的活儿,就是你不用干的活儿。在扩展干活的机器之前,先减少活儿本身——用缓存和 CDN。

Cache — 别把同一个答案算两遍

一位厨师早上把热门酱汁一次做好,而不是每来一单就从头做一遍。

Cache(缓存)保存昂贵工作的结果——一次数据库查询、一个算好的页面、一次 API 调用——这样下一个请求能立刻拿到,而不用重做。数据库前面放一个快速的内存存储(Redis、Memcached),就能吸收大部分读请求。最常见的方案是 cache-aside:先查缓存;没命中(miss)时,去查数据库,并把答案放进缓存。

AppCacheRedis · MemcachedDatabaseslow · expensive1 · check2 · miss → DB3 · storefor next timehit = answer from memory, the database is never touched
Cache-aside:先查缓存 → 没命中就问数据库 → 把答案存下来留给下次。
PatternCache· cache-aside
问题
数据库一遍又一遍地算出相同的答案。
思路
把结果记在快速内存里,再次直接返回。
例子
商品页缓存在 Redis 里——数据库不再重新计算它。
代价
多了一份"真相"的副本,它可能会过期。
何时使用
你那个不堪重负的数据库,往往真正需要的是缓存,而不是更多服务器。
关联
需要谨慎的 Cache Invalidation;它在网络边缘的亲戚是 CDN。

CDN — 从就近的地方服务用户

一家全球连锁店,在各地的本地仓库里都备着同样的货——顾客从街角就能拿到,而不是从海那边运过来。

**CDN(Content Delivery Network,内容分发网络)**把你的静态内容——图片、脚本、视频,以及越来越多的整页内容——的副本存放在世界各地的数据中心(edge,边缘),由离用户最近的那个来服务。它大幅降低延迟,并为你的 origin(源站)——也就是你自己的服务器——卸下巨大的负载。

Originyour serversEdge · Europeimages · JS · pagesEdge · USimages · JS · pagesEdge · Asiaimages · JS · pagesEdge · LatAmimages · JS · pageseach user is served by the nearest copy
源站把副本交给不同区域的边缘节点;每个用户由最近的那个来应答。
ToolCDN· Content Delivery Network
问题
地球另一端的用户,要等字节横跨一个大洋。
思路
把静态内容的副本放在离用户近的节点上。
例子
网站图片从法兰克福的节点发出,而不是美国的服务器。
代价
同样的缓存问题:内容一变,副本就得刷新。
何时使用

凡是全球性的、静态的内容——在你的服务器看到请求之前,CDN 就把重活干完了。

关联
边缘的 Cache;同样受 Cache Invalidation 困扰。

Cache Invalidation — 难的部分

冰箱上的日历比看手机更快——直到有人在手机上改了计划,于是冰箱上的那份就在说谎了。

缓存是"真相"的第二份副本,而副本可能会过期(stale data,陈旧数据)。真正难的问题不是加一个缓存——而是知道什么时候把它扔掉;这就是 cache invalidation(缓存失效)。缓存那些读得远多于写的东西,设置合理的过期时间(TTL),并且始终清楚缓存出错时会发生什么。

DatabaseCacheprice = 10price = 12updateprice = 10price = 10stale!nobody told the cachethe cache keeps serving the old valuefix: delete on write · set a TTL (expiry)
数据库里的价格变了,但缓存不知道,继续返回旧价格。
ConceptCache Invalidation· 让过期副本失效
问题
用户从缓存里看到过时的数据。
思路
数据变化时删除缓存条目,并且/或者给它设一个生存期(TTL)。
例子

价格变化时,从 Redis 里删除 product:42 这个键;另外设一个 5 分钟的 TTL 以防万一。

陷阱
某条被遗忘的写入路径没有清缓存——数据就悄悄地撒了好几个小时的谎。
关联
每个 Cache 和 CDN 的另一面。

最便宜的请求,是你根本不用服务的那个。缓存热点内容,让静态内容从边缘发出,并在失效问题上多下功夫。

§ 05

数据库迟早会成为瓶颈——它是唯一真正难以扩展的部分,因为它保存着所有人共享的"真相"。

Database Bottleneck — 数据库通常就是那堵墙

十个收银台全都把手伸进同一个仓库——收银员可以扩展,但唯一的那扇仓库门成了堵点。

你可以运行一百台无状态的应用服务器,但它们通常共用一个数据库——这就成了上限。和应用服务器不同,你不能简单地克隆它,因为每个副本都必须在数据上达成一致。所以扩展数据库,是扩展一个系统中最难、最需要谨慎的部分。

appappappappappDatabaseone shared truthapp servers clone easily · the database can't
应用服务器很容易成倍增加,但它们全都撞上同一个共享数据库。
ConceptDatabase Bottleneck· 那堵墙
是什么
每台应用服务器都会撞上的那个共享数据库。
为什么
数据库的各个副本必须在数据上一致——你不能直接克隆它。
怎么办
先用缓存尽量往后拖;然后上副本;sharding 放在最后。
关联
用 Cache、Read Replica、Sharding 来处理。

Read Replica — 为读请求准备的副本

把参考书的复印件分发出去,让很多人能同时阅读,而唯一的原本是唯一有人往里写的那一本。

大多数应用读得远比写得多。Read replicas(只读副本)是数据库的副本,负责处理读查询,把读负载分摊到多台机器上,而写入仍然只去一个主库(primary)。这是扩展数据库的第一招,也是最容易的一招——但副本会稍微落后一点(replication lag,复制延迟),所以刚写入的值可能不会立刻出现在副本上。

AppPrimaryall writesRead replicaRead replicaRead replicawritesreadsreplicas copy the primary — with a small lag
写入只去主库;读请求分散到各个副本上。副本带着一点延迟追上来。
PatternRead Replica· 只读副本
问题
数据库被读查询淹没了。
思路
数据库的副本回答读请求;所有写入都去一个主库。
例子
信息流和搜索从三个副本读取;订单写入主库。
代价
复制延迟:用户保存了个人资料,刷新页面——看到的却是旧的。
何时使用
负载主要是读,而缓存已经不够用的时候。
关联
Cache 之后的下一级台阶;如果问题出在写入——用 Sharding。

Sharding — 把写入拆开

一本写满的账簿变成几本——A–M 在一本里,N–Z 在另一本里——这样两位职员可以同时记账,而不必争抢同一页纸。

当连写入都超出一台机器的能力时,你就要做 shard(分片):按某个键——shard key(分片键)——把数据拆到几个数据库里,每个负责一部分。它能解锁巨大的规模,但确实很难——跨分片查询会变得痛苦,而分片键几乎是个一旦选定就改不了的决定。

Shard routershard key = customer nameShard 1A – HShard 2I – PShard 3Q – Zeach shard holds a slice — writes are split toocross-shard queries hurt · the key is hard to change
路由器看着键,把数据送到正确的分片。每个分片保存自己的那一部分。
PatternSharding· 水平分区
问题
写入多到一个主库跟不上。
思路
按键把数据切成几个独立的数据库。
例子
客户 A–H 在分片 1,I–P 在分片 2,Q–Z 在分片 3。
代价
跨分片的查询很痛苦;分片键几乎不可能再改。
何时使用
这是深水区;放到最后才去,在副本和缓存真的不够用的时候。
关联
Scaling Ladder 的最后一级台阶;排在 Read Replica 之后。

应用服务器很容易克隆;数据库不行。在分片之前,先长时间地缓存和复制。

§ 06

不是所有工作都必须在用户等待时完成。把慢的活儿移出请求,在各部分之间放上缓冲,系统才能保持快速、扛住峰值。

Background Job — 把慢活儿移出请求路径

在餐厅里,他们接了你的单,你就坐下——不会让你一直站在柜台前,等到饭菜做好。

当一个请求触发了某件很慢的事——发邮件、生成报表、处理上传——别让用户等着。把它交给后台队列,然后立刻返回;稍后由一个 worker 来做慢的那部分。这就是 background job(后台任务)。页面保持敏捷,而重活在一旁完成。(这是协议课和架构课里讲过的消息传递思想。)

UserAppQueueWorkerinstant replyenqueueSend emailslow · secondsthe user waits only for the fast partthe slow work happens off to the side
应用把任务放进队列,立刻回复。一个 worker 发送邮件——稍后,在一旁完成。
PatternBackground Job· 异步处理
问题
服务器干慢活儿时,用户要等上好几秒。
思路
把任务放进队列,立即回复,让 worker 去干活。
例子
注册之后,欢迎邮件在后台发送——页面立刻打开。
代价
结果不是即时的;你需要一个队列,以及处理 worker 错误的办法。
何时使用
凡是用户不需要马上看到的事情。
关联
同一个队列还给你带来 Load Leveling 和 Decoupling。

Load Leveling — 队列吸收峰值

洪水泛滥的河流和城镇之间的一座水库——洪峰灌满水库而不是淹没街道,然后以稳定的速度排出去。

队列同时也是一个缓冲。这个技巧叫做 queue-based load leveling(基于队列的负载平滑)。当流量突然飙升——一次发布、一次爆红——工作堆积在队列里,worker 按自己的节奏把它清空,而不是让洪水冲垮系统。没有这个缓冲,一个到来速度快过你处理速度的峰值,会把一切都拖垮。

spiky trafficqueue = bufferworkers: steady pacea spike becomes a backlog, not a crash
左——起伏不定的进入流量;中——队列存住工作;右——worker 以稳定的速度运行。
PatternLoad Leveling· queue-based load leveling
问题
流量峰值到来的速度,快过系统能处理的速度。
思路
队列存住工作;worker 以恒定的速度把它清空。
例子
发布时,涌来平时十倍的订单——它们在队列里等着,而不是把服务打垮。
代价
峰值期间工作会延迟完成;队列需要监控。
关联
**队列把致命的峰值变成了可以应付的积压。**是 Background Job 的亲戚。

Decoupling — 一个慢的部分不会拖垮其余

船上的防水隔舱——一个进水了,隔板让其他隔舱保持干燥,船继续漂着。

有了 decoupling(解耦),服务之间通过队列和事件通信,而不是直接的阻塞调用,所以一个变慢或失败的组件**不会冻住所有等着它的人。**即使邮件服务挂了,订单照样能接下来;邮件只是稍后补上。解耦让大系统在压力下能优雅地降级,而不是一下子全部垮掉。

Direct, blocking callOrder serviceEmail servicedown✗ order fails tooThrough a queueOrder serviceQueueEmail servicedown✓ the order is taken · email catches up later
上——直接调用:邮件服务挂了,所以订单也失败了。下——通过队列:订单被接下,邮件稍后再发。
ConceptDecoupling· 通过队列和事件通信
问题
一个失败的服务,拖垮所有调用它的服务。
思路
通过队列或事件通信,而不是等待直接的回答。
例子
订单服务发布"订单已创建";邮件服务恢复后再把它取走。
代价
更难追踪发生了什么;数据只是最终一致,而不是即时一致。
关联

Design for Failure 的基础;建立在和 Background Job 相同的队列之上。

只让用户等他现在需要的东西。其他一切都放到队列后面——峰值来的时候,这也会救你一命。

§ 07

扩展做得好,大半靠克制和次序:测量真正的瓶颈,先爬便宜的台阶,并为更多机器带来的故障做好准备。

Measure First — 扩展之前先测量

医生动手术前先做检查——因为猜测而切错了器官,对谁都没好处。

在添加任何东西之前,先用指标和 profiling(性能剖析)找出真正的瓶颈——是数据库、一条慢查询、网络,还是 CPU?工程师经常去优化错误的东西,只因为它感觉慢。数据会告诉你系统真正疼在哪里;扩展那里,别的都不要动。(这正是 observability 派上用场的地方。)

one request, 400 ms — where does the time go?app code40 msDB query280 msnetwork50 msrender30 msfix the long bar — not the one that merely felt slow
把一个请求拆开来看:几乎所有时间都花在一条数据库查询上。该修的就是它。
PracticeMeasure First· profiling
问题
人们优化"感觉"慢的地方,而瓶颈其实在别处。
思路
指标和 profiling 显示时间到底花在哪里。
例子

一个请求耗时 400 毫秒,其中 280 毫秒是一条数据库查询。所以你需要的是索引或缓存,而不是更多服务器。

代价
你需要 observability:指标、链路追踪、日志。
关联
找出 Bottleneck;防范 Premature Scaling。

Scaling Ladder — 按顺序爬便宜的台阶

你会先加保温层、封好窗户,然后才去买第二台锅炉——便宜的办法在先。

有一个顺序,从最便宜、最容易的开始:一台更大的机器,然后是缓存和 CDN,然后是 load balancer 后面的无状态服务器做水平扩展,然后是 read replicas,然后是为慢活儿准备的队列——最后到最远的一端,才是 sharding。每一级台阶都意味着更多工作和更多复杂度。

1 · Bigger box2 · Cache + CDN3 · Stateless + LB4 · Read replicas5 · Queues6 · Shardingcost ↑climb only as far as the load forces you
六级台阶:越往上,越贵、越复杂。只有负载逼你的时候才往上爬。
PracticeScaling Ladder· 先用最便宜的办法
问题
人们一上来就抓起最强大、最复杂的工具。
思路

一级一级来:更大的机器 → 缓存和 CDN → 无状态 + load balancer → 副本 → 队列 → sharding。

例子
网站慢了——先加内存、加缓存;也许永远轮不到 sharding。
何时使用
始终。只爬到你的负载逼你爬到的高度。
关联
把第 2–6 章的每个概念串在一起。

Design for Failure — 机器越多,故障越多

一盏灯泡在某个晚上很少会坏;在一座有一万盏灯的体育场里,总有几盏是灭的。规模一大,总有东西是坏的。

随着机器增多,在任何时刻总有某个东西在出故障的概率,会趋向于确定。所以扩展和可靠性是同一个项目:冗余——没有单点故障(SPOF);health checks 和 failover;以及 graceful degradation(优雅降级),让一个坏掉的部分只是降低质量,而不是让整个系统死掉。

LBLB standbyappappdownappDB primaryDB standbyfailoverredundancy · no single point of failure · degrade, don't die
每个组件都有备份:第二个 load balancer、多台服务器、一个带 failover 的备用数据库。
PracticeDesign for Failure· 冗余、优雅降级
问题
规模一大,总有某台机器是坏的。
思路

冗余、health checks、failover 和 graceful degradation——一个坏掉的部分不会拖垮整体。

例子
推荐服务挂了——商店展示的页面少了"你可能还喜欢"那一块,但照常卖货。
代价
备用机器要花钱;failover 必须定期演练。
关联
汇集了 Health Check、Rolling Deploy(failover)和 Decoupling。

这一切如何串起来

旅程结束后的地图:现在你看得出哪些站在同一条线上。

这门课里的概念不是互不相干的小技巧,而是一条链。测量 → 找到 bottleneck → 爬到你需要的那一级 ladder 台阶。水平扩展需要无状态服务器和 load balancer;load balancer 需要 health checks;缓存需要失效机制;队列给你带来 leveling 和 decoupling。而这一切都服务于同一个目标:一个部分出故障,不能让整个系统死掉。

Measure firstBottleneckScaling ladderVerticalCache · CDNHorizontalRead replicaQueuesShardingInvalidationStatelessLoad balancerLoad levelingDecouplingRolling deployHealth checkDesign for failuremeasure → find the bottleneck → climb only the rungs you need
课程中各概念之间的主要联系。
扩展任何东西之前
  • 我测量过了吗,真正的瓶颈在哪里——而不是猜的?
  • 在这一级之前,有没有更便宜的台阶——更大的机器、缓存、CDN?
  • 我的应用服务器是无状态的吗,这样我才能加更多?
  • 数据库的负载是读(副本、缓存),还是写(难走的路)?
  • 慢活儿能不能挪到队列后面?
  • 我真的有这么大的负载吗——还是在为一个也许不会到来的未来而建设?
过度设计的气味测试
  • 给一个一台机器就能轻松装下的数据库做 sharding。
  • 给只有一百个用户的应用上微服务和队列。
  • 去扩展并不是瓶颈的那一部分,只因为它感觉慢。
  • 一个缓存层纠缠得没人敢确定它什么时候过期。
  • 为你没有的几百万用户,和一场还没发生的发布而建设。
你扩展得好的迹象
  • 你扩展的是测量出来的瓶颈,而下一个现在已经看得见了。
  • 服务器是无状态的;增加或失去一台都无关紧要。
  • 读请求被缓存并复制;数据库得以喘息。
  • 慢活儿跑在队列后面,峰值变成积压,而不是崩溃。
  • 没有单点故障——任何一台机器都能悄无声息地挂掉。
  • 你只在负载要求的范围内往阶梯上爬。

速查表:全部 21 个概念

基础

Scalability应对增长

在保持快速、保持在线的同时承受更多负载。

Bottleneck卡口

最慢的那一环;它限制着整个系统。

Premature Scaling扩展过早

为你没有的负载去建设。有成本,没回报。

垂直与水平

Vertical Scalingscale up

一台更大的机器。简单,但有天花板,也是单点故障。

Horizontal Scalingscale out

许多相同的副本。几乎没有上限,需要协调。

Stateless Server无本地状态

请求之间什么都不记——任何一台服务器都能处理任何请求。

把流量分散开

Load BalancerLB

一个前门,把请求分给各台服务器。

Health Checkliveness probe

死掉的服务器被自动摘出轮转。

Rolling Deploy零停机部署、failover

每次更新几台服务器;一台挂了,流量转移到其余的。

少干点活

Cachecache-aside

记住一个昂贵的答案,再次从内存里返回。

CDNContent Delivery Network

在全球各地靠近用户的地方存放静态内容的副本。

Cache Invalidation让过期副本失效

知道什么时候该扔掉一份副本:写入时删除,加上 TTL。

扩展数据

Database Bottleneck那堵墙

一个无法简单克隆的共享数据库。

Read Replica只读副本

副本负责读,写入去一个主库。有一点延迟。

Sharding水平分区

按键把数据拆到几个数据库里。最后的手段。

削峰与解耦

Background Job异步处理

慢活儿交给队列;用户不用等。

Load Leveling队列当缓冲

队列把峰值变成积压,而不是崩溃。

Decoupling队列和事件

通过队列和事件通信——一个坏掉的部分不会拖垮其余。

理智地扩展

Measure Firstprofiling

指标和 profiling 显示真正的瓶颈。

Scaling Ladder先用最便宜的办法

机器 → 缓存/CDN → 无状态 + LB → 副本 → 队列 → sharding。

Design for Failure冗余

冗余、failover 和优雅降级:规模一大,总有东西是坏的。

扩展你测量出来的那个瓶颈,用能解决它的最便宜的工具,并且假定规模一大总有东西是坏的。其余的一切都是过早的。

速成课结束 · 7 章 · 21 个概念 · 先测量再猜

接下来是深度:再读一遍《Designing Data-Intensive Applications》、《System Design Interview》系列,以及那些真正扛过规模的公司的工程博客。但把上面那个核心想法,举得高过那些套路——一个系统在负载下,只跟它最弱的那一点一样强。找出那一点,把它拓宽,而在你不得不动手之前,别去建造其余的。