速成课 · No. 09
扩展不是让一切都变快——而是找出在负载下最先撑不住的那一处,把它拓宽,然后是下一处。大多数系统需要这么做的远比建造者担心的要少;真正的本事,是知道该伸手去拿什么、又在什么时候去拿。
21 个概念 · 每个配一幅图 · 末尾有速查表
扩展不是你一按就灵的某个开关——它是一项持续的活儿:扛住更多负载而不倒下。它从一个想法起步:找出最先崩掉的那一处。
Scalability — 扛住更多而不崩
一家小咖啡馆运转良好,直到一辆旅游大巴停了下来——突然之间,那一个咖啡师、那一台收银机、那几张桌子都跟不上了。扩展就是为这辆大巴做准备。
Scalability(可扩展性)是系统应对增长的能力——更多用户、更多请求、更多数据——同时保持快速、保持在线。它不是比谁最聪明;而是在负载成倍增加时,系统不会撑不住。
- 是什么
- 在不变慢、不宕机的前提下,承受更多负载的能力。
- 如何运作
- 几乎所有扩展问题都归结为一件事:某个地方成了跟不上的那一环。
- 例子
- 一辆旅游大巴刚刚停在门口的那家咖啡馆。
- 关联
靠 Vertical Scaling 和 Horizontal Scaling 增长;会撞上 Bottleneck。
Vertical vs Horizontal — 向上还是向外
想多运货,你可以买一辆更大的卡车——但大也有个限度——或者买更多卡车:几乎没有上限,但现在你需要一个调度员。
增加容量恰好有两种办法。**Vertical scaling(scale up,纵向扩展)**是换一台更大的机器——更多 CPU、更多内存。简单,但有一个硬性的天花板,而且仍然是单点故障。**Horizontal scaling(scale out,横向扩展)**是让更多机器一起干活——几乎没有上限,但需要协调(负载均衡、共享状态)。垂直扩展为你争取时间;**真正的规模在水平扩展里。**细节见第 2 章。
Bottleneck — 最窄的那一点
一条公路的速度取决于它最窄的那一段——把其他每条车道都拓宽也没用,除非你先解决那个卡口。
Bottleneck(瓶颈)是系统里最慢的那一环,它限制着其他一切。系统的速度只和那一环一样快。把资源砸在错的地方,就是白白烧钱;要做的是找出真正的瓶颈——数据库、某个慢服务、网络——把它拓宽,然后找下一个。
- 是什么
- 那个跟不上、拖慢整个流程的环节。
- 如何运作
拓宽一个瓶颈,下一个就会冒出来。扩展是一连串的瓶颈,不是一次性的修复。
- 例子
CDN 和应用服务器每秒能处理几千个请求,数据库只能处理八百个。再加服务器也无济于事。
- 陷阱
- 人们很容易去"修"那些只是感觉慢的地方,而不是真正慢的地方。
- 关联
- 用 Measure First 找出来;用 Scaling Ladder 的各级台阶来处理。
Premature Scaling — 为你还没有的负载提前建设
你不会为通往林中小屋的路修一条六车道的高速公路。一条好路就能载下所有会来的人。
Premature scaling(过早扩展)就是为你并没有的负载去建设。一台像样的服务器加一个数据库,能服务的用户数多得惊人。在还没有负载之前就上 sharding、微服务、复杂的缓存——这是你不需要的成本和复杂度。
- 问题
- 复杂度和金钱花在了并不存在、也许永远不会来的负载上。
- 例子
- 给一个只有一百个用户的应用上分片数据库和微服务。
- 补救
- 先测量;在真正疼的时候,去扩展真正疼的那一处。
- 关联
- 解药是 Measure First 和 Scaling Ladder。
全景图:七组共 21 个概念
一张地铁线路图:先看到整条线路,再一站一站地坐过去。
课程的每一章就是一组概念。它们大致按一个不断成长的系统遇到它们的顺序排列:先是基础,然后是更多服务器、分散流量、缓存、数据库、队列——最后是头脑清醒地运用这一切的规则。
如何阅读每个概念
一本护照:每个人的栏目都一样,所以很容易比较。
从这里开始,每个概念都有相同的几个部分:英文名称(记住它——这是你在文档和面试里会遇到的术语)、一幅生活中的画面、一张图和一张卡片:问题、思路、例子、代价、何时使用,以及它与其他概念的关联。最后有一份速查表,把所有术语放在一页上。
扩展不是让一切都变快。而是找到瓶颈,把它拓宽,然后重复。
沿着扩展阶梯迈出的头几步:先把一台大机器用到头,再让应用能跑在任意多台相同的机器上。
Vertical Scaling — 先换台更大的机器
厨房忙不过来时,第一个办法是换个更大的灶台、加更多操作台——而不是开第二家餐厅。
**Vertical scaling(scale up)**是最简单的一招:一台更大的机器。不用改代码,很快就能换来实实在在的余量——往往够用很长时间。但它有一个硬性的天花板(你买不到无限大的服务器),而且这一台机器仍然是单点故障。
- 问题
- 服务器扛不住负载:CPU 或内存不够。
- 思路
- 换一台 CPU 和内存更多的机器。
- 例子
- 数据库从 8 核服务器换成 64 核的。
- 代价
- 有硬性的天花板;一台机器 = 一个单点故障。
- 何时使用
- 最先用。用它来争取时间,而不是当作终点。
- 关联
- Scaling Ladder 的第一级台阶;下一步是 Horizontal Scaling。
Horizontal Scaling — 许多台机器,干同一件事
一个收银台变成十个——现在十位顾客可以同时被服务,而且如果有一台卡住了,其余九台照常运转。
Horizontal scaling(scale out)意思是运行应用的许多个副本,把工作分摊到它们身上。它几乎没有上限,而且能扛住任何一台机器的损失。难点在协调:得有东西来分发请求(第 3 章),而且各个副本不能各自持有私有状态——这就是下一个关键想法。
- 问题
- 一台机器,哪怕是最大的,也会撞上天花板。
- 思路
- 运行 N 个相同的应用副本,把请求分摊给它们。
- 例子
- 黑色星期五,一家商店运行 20 个副本,而不是平时的 4 个。
- 代价
- 需要协调:一个 load balancer,和一个放状态的共享位置。
- 何时使用
- 垂直扩展不够用,或者需要扛住机器宕机的时候。
- 关联
- 只有配合 Stateless Server 才行得通;需要 Load Balancer。
Stateless Server — 任何一台机器都能处理任何请求
一个呼叫中心,任何一位坐席都能接任何一通电话,因为客户的所有资料都存在共享系统里——而不是在某一位坐席的脑子里。
要让水平扩展奏效,应用服务器必须是**无状态(stateless)**的:在请求之间,它们不在自己的内存里保留任何关于用户的东西。会话数据、上传的文件、进度——全都存在某个共享的地方(数据库、缓存、客户端携带的 token)。这样任何请求都可以落到任何一台服务器上,增加或失去一台机器都不会有什么改变。
- 问题
- 服务器把会话存在自己的内存里——用户被"钉"在它身上,一旦崩溃,一切都丢了。
- 思路
- 服务器在请求之间什么都不保留;状态存在数据库、缓存或客户端的 token 里。
- 例子
- 购物车存在 Redis 里,而不是进程内存里;登录就是一个 token。
- 代价
- 每个请求都要从共享存储里取回自己的状态。
- 何时使用
只要你打算用不止一台服务器,就始终用。正是无状态,才让"多加几台服务器"真正行得通。
- 关联
- 是 Horizontal Scaling 和 Rolling Deploy 的前提。
垂直扩展买来时间;水平扩展买来规模。而水平扩展只有在你的服务器自己什么都不记的时候才行得通。
一旦有了许多台服务器,就得有东西站在前面,决定每个请求由谁处理。这就是 load balancer——扩展后系统里的交通警察。
Load Balancer — 一个前门,许多服务器
银行里的引导员,把每位顾客指给任何一个空闲的柜员——这样就不会有哪个柜员被淹没,而其他柜员闲着。
Load balancer(负载均衡器)——Nginx、HAProxy、云厂商的均衡器——是唯一的入口,它按简单的规则把进来的请求分给你的服务器池:round-robin(轮询),或者发给最闲的那台(least connections,最少连接)。客户端找的是它,而不是某一台具体的服务器,所以你可以在它后面悄无声息地增减机器。
- 问题
- 有许多台服务器——谁来决定每个请求去哪里?
- 思路
- 一个入口把请求分给一池服务器。
- 例子
- Nginx 放在某个 API 的四个副本前面,按 round-robin 分发请求。
- 代价
- 多了一个组件,而它自己不能变成单点故障。
- 何时使用
- 只要你有不止一台服务器。
- 关联
- **它把一堆服务器变成一个系统。**使用 Health Checks。
Health Check — 别再往死掉的机器上发请求
引导员发现某个柜员离开了,就干脆不再把人派到那个窗口——没人会在空桌子前干等。
有了 health check(健康检查),load balancer 会不断检查哪些服务器是健康的,并绕开不健康的那些。宕机或过载的机器会被自动摘出轮转,流量流向其余机器。扩展后的系统就是这样在一台机器挂掉时,让用户毫无察觉的。
- 问题
- Load balancer 还在往一台已经死掉的服务器发请求。
- 思路
- 定期探测每台服务器;没有回应——摘出轮转;恢复了——放回去。
- 例子
- 每隔几秒
GET /health;没有200——这台服务器被摘掉。 - 代价
- 写得不好的检查会撒谎:数据库已经连不上了,它还说"健康"。
- 关联
Load Balancer 的一部分;是 failover 和 Design for Failure 的基础。
Rolling Deploy 与 Failover — 白捡的好处
你可以一次翻修一条收银通道,而其他通道继续营业——商店从不关门。
一旦流量通过均衡器流向可互换的服务器,一些强大的能力几乎是白送的:零停机(滚动)部署——每次更新几台服务器;failover(故障切换)——一台挂了,流量转移到其余的;还有快速的回滚。均衡器加上无状态服务器,是让系统既可扩展又有韧性的骨架。
- 问题
- 发布新版本就意味着"网站停机"。
- 思路
- 每次更新几台服务器,其余的继续扛流量。
- 例子
- 四台服务器里,两台已经是 v2,一台正在更新,一台还在 v1——用户毫无察觉。
- 代价
- 有一段时间两个版本同时运行——它们得能和平共处。
- 关联
- 需要 Stateless Server、Load Balancer 和 Health Checks。
Load balancer 把许多台服务器变成一个地址——也把一台机器的死亡变成一件无关紧要的事。
最快的活儿,就是你不用干的活儿。在扩展干活的机器之前,先减少活儿本身——用缓存和 CDN。
Cache — 别把同一个答案算两遍
一位厨师早上把热门酱汁一次做好,而不是每来一单就从头做一遍。
Cache(缓存)保存昂贵工作的结果——一次数据库查询、一个算好的页面、一次 API 调用——这样下一个请求能立刻拿到,而不用重做。数据库前面放一个快速的内存存储(Redis、Memcached),就能吸收大部分读请求。最常见的方案是 cache-aside:先查缓存;没命中(miss)时,去查数据库,并把答案放进缓存。
- 问题
- 数据库一遍又一遍地算出相同的答案。
- 思路
- 把结果记在快速内存里,再次直接返回。
- 例子
- 商品页缓存在 Redis 里——数据库不再重新计算它。
- 代价
- 多了一份"真相"的副本,它可能会过期。
- 何时使用
- 你那个不堪重负的数据库,往往真正需要的是缓存,而不是更多服务器。
- 关联
- 需要谨慎的 Cache Invalidation;它在网络边缘的亲戚是 CDN。
CDN — 从就近的地方服务用户
一家全球连锁店,在各地的本地仓库里都备着同样的货——顾客从街角就能拿到,而不是从海那边运过来。
**CDN(Content Delivery Network,内容分发网络)**把你的静态内容——图片、脚本、视频,以及越来越多的整页内容——的副本存放在世界各地的数据中心(edge,边缘),由离用户最近的那个来服务。它大幅降低延迟,并为你的 origin(源站)——也就是你自己的服务器——卸下巨大的负载。
- 问题
- 地球另一端的用户,要等字节横跨一个大洋。
- 思路
- 把静态内容的副本放在离用户近的节点上。
- 例子
- 网站图片从法兰克福的节点发出,而不是美国的服务器。
- 代价
- 同样的缓存问题:内容一变,副本就得刷新。
- 何时使用
凡是全球性的、静态的内容——在你的服务器看到请求之前,CDN 就把重活干完了。
- 关联
- 边缘的 Cache;同样受 Cache Invalidation 困扰。
Cache Invalidation — 难的部分
冰箱上的日历比看手机更快——直到有人在手机上改了计划,于是冰箱上的那份就在说谎了。
缓存是"真相"的第二份副本,而副本可能会过期(stale data,陈旧数据)。真正难的问题不是加一个缓存——而是知道什么时候把它扔掉;这就是 cache invalidation(缓存失效)。缓存那些读得远多于写的东西,设置合理的过期时间(TTL),并且始终清楚缓存出错时会发生什么。
- 问题
- 用户从缓存里看到过时的数据。
- 思路
- 数据变化时删除缓存条目,并且/或者给它设一个生存期(TTL)。
- 例子
价格变化时,从 Redis 里删除
product:42这个键;另外设一个 5 分钟的 TTL 以防万一。- 陷阱
- 某条被遗忘的写入路径没有清缓存——数据就悄悄地撒了好几个小时的谎。
- 关联
- 每个 Cache 和 CDN 的另一面。
最便宜的请求,是你根本不用服务的那个。缓存热点内容,让静态内容从边缘发出,并在失效问题上多下功夫。
数据库迟早会成为瓶颈——它是唯一真正难以扩展的部分,因为它保存着所有人共享的"真相"。
Database Bottleneck — 数据库通常就是那堵墙
十个收银台全都把手伸进同一个仓库——收银员可以扩展,但唯一的那扇仓库门成了堵点。
你可以运行一百台无状态的应用服务器,但它们通常共用一个数据库——这就成了上限。和应用服务器不同,你不能简单地克隆它,因为每个副本都必须在数据上达成一致。所以扩展数据库,是扩展一个系统中最难、最需要谨慎的部分。
- 是什么
- 每台应用服务器都会撞上的那个共享数据库。
- 为什么
- 数据库的各个副本必须在数据上一致——你不能直接克隆它。
- 怎么办
- 先用缓存尽量往后拖;然后上副本;sharding 放在最后。
- 关联
- 用 Cache、Read Replica、Sharding 来处理。
Read Replica — 为读请求准备的副本
把参考书的复印件分发出去,让很多人能同时阅读,而唯一的原本是唯一有人往里写的那一本。
大多数应用读得远比写得多。Read replicas(只读副本)是数据库的副本,负责处理读查询,把读负载分摊到多台机器上,而写入仍然只去一个主库(primary)。这是扩展数据库的第一招,也是最容易的一招——但副本会稍微落后一点(replication lag,复制延迟),所以刚写入的值可能不会立刻出现在副本上。
- 问题
- 数据库被读查询淹没了。
- 思路
- 数据库的副本回答读请求;所有写入都去一个主库。
- 例子
- 信息流和搜索从三个副本读取;订单写入主库。
- 代价
- 复制延迟:用户保存了个人资料,刷新页面——看到的却是旧的。
- 何时使用
- 负载主要是读,而缓存已经不够用的时候。
- 关联
- Cache 之后的下一级台阶;如果问题出在写入——用 Sharding。
Sharding — 把写入拆开
一本写满的账簿变成几本——A–M 在一本里,N–Z 在另一本里——这样两位职员可以同时记账,而不必争抢同一页纸。
当连写入都超出一台机器的能力时,你就要做 shard(分片):按某个键——shard key(分片键)——把数据拆到几个数据库里,每个负责一部分。它能解锁巨大的规模,但确实很难——跨分片查询会变得痛苦,而分片键几乎是个一旦选定就改不了的决定。
- 问题
- 写入多到一个主库跟不上。
- 思路
- 按键把数据切成几个独立的数据库。
- 例子
- 客户 A–H 在分片 1,I–P 在分片 2,Q–Z 在分片 3。
- 代价
- 跨分片的查询很痛苦;分片键几乎不可能再改。
- 何时使用
- 这是深水区;放到最后才去,在副本和缓存真的不够用的时候。
- 关联
- Scaling Ladder 的最后一级台阶;排在 Read Replica 之后。
应用服务器很容易克隆;数据库不行。在分片之前,先长时间地缓存和复制。
不是所有工作都必须在用户等待时完成。把慢的活儿移出请求,在各部分之间放上缓冲,系统才能保持快速、扛住峰值。
Background Job — 把慢活儿移出请求路径
在餐厅里,他们接了你的单,你就坐下——不会让你一直站在柜台前,等到饭菜做好。
当一个请求触发了某件很慢的事——发邮件、生成报表、处理上传——别让用户等着。把它交给后台队列,然后立刻返回;稍后由一个 worker 来做慢的那部分。这就是 background job(后台任务)。页面保持敏捷,而重活在一旁完成。(这是协议课和架构课里讲过的消息传递思想。)
- 问题
- 服务器干慢活儿时,用户要等上好几秒。
- 思路
- 把任务放进队列,立即回复,让 worker 去干活。
- 例子
- 注册之后,欢迎邮件在后台发送——页面立刻打开。
- 代价
- 结果不是即时的;你需要一个队列,以及处理 worker 错误的办法。
- 何时使用
- 凡是用户不需要马上看到的事情。
- 关联
- 同一个队列还给你带来 Load Leveling 和 Decoupling。
Load Leveling — 队列吸收峰值
洪水泛滥的河流和城镇之间的一座水库——洪峰灌满水库而不是淹没街道,然后以稳定的速度排出去。
队列同时也是一个缓冲。这个技巧叫做 queue-based load leveling(基于队列的负载平滑)。当流量突然飙升——一次发布、一次爆红——工作堆积在队列里,worker 按自己的节奏把它清空,而不是让洪水冲垮系统。没有这个缓冲,一个到来速度快过你处理速度的峰值,会把一切都拖垮。
- 问题
- 流量峰值到来的速度,快过系统能处理的速度。
- 思路
- 队列存住工作;worker 以恒定的速度把它清空。
- 例子
- 发布时,涌来平时十倍的订单——它们在队列里等着,而不是把服务打垮。
- 代价
- 峰值期间工作会延迟完成;队列需要监控。
- 关联
- **队列把致命的峰值变成了可以应付的积压。**是 Background Job 的亲戚。
Decoupling — 一个慢的部分不会拖垮其余
船上的防水隔舱——一个进水了,隔板让其他隔舱保持干燥,船继续漂着。
有了 decoupling(解耦),服务之间通过队列和事件通信,而不是直接的阻塞调用,所以一个变慢或失败的组件**不会冻住所有等着它的人。**即使邮件服务挂了,订单照样能接下来;邮件只是稍后补上。解耦让大系统在压力下能优雅地降级,而不是一下子全部垮掉。
- 问题
- 一个失败的服务,拖垮所有调用它的服务。
- 思路
- 通过队列或事件通信,而不是等待直接的回答。
- 例子
- 订单服务发布"订单已创建";邮件服务恢复后再把它取走。
- 代价
- 更难追踪发生了什么;数据只是最终一致,而不是即时一致。
- 关联
Design for Failure 的基础;建立在和 Background Job 相同的队列之上。
只让用户等他现在需要的东西。其他一切都放到队列后面——峰值来的时候,这也会救你一命。
扩展做得好,大半靠克制和次序:测量真正的瓶颈,先爬便宜的台阶,并为更多机器带来的故障做好准备。
Measure First — 扩展之前先测量
医生动手术前先做检查——因为猜测而切错了器官,对谁都没好处。
在添加任何东西之前,先用指标和 profiling(性能剖析)找出真正的瓶颈——是数据库、一条慢查询、网络,还是 CPU?工程师经常去优化错误的东西,只因为它感觉慢。数据会告诉你系统真正疼在哪里;扩展那里,别的都不要动。(这正是 observability 派上用场的地方。)
- 问题
- 人们优化"感觉"慢的地方,而瓶颈其实在别处。
- 思路
- 指标和 profiling 显示时间到底花在哪里。
- 例子
一个请求耗时 400 毫秒,其中 280 毫秒是一条数据库查询。所以你需要的是索引或缓存,而不是更多服务器。
- 代价
- 你需要 observability:指标、链路追踪、日志。
- 关联
- 找出 Bottleneck;防范 Premature Scaling。
Scaling Ladder — 按顺序爬便宜的台阶
你会先加保温层、封好窗户,然后才去买第二台锅炉——便宜的办法在先。
有一个顺序,从最便宜、最容易的开始:一台更大的机器,然后是缓存和 CDN,然后是 load balancer 后面的无状态服务器做水平扩展,然后是 read replicas,然后是为慢活儿准备的队列——最后到最远的一端,才是 sharding。每一级台阶都意味着更多工作和更多复杂度。
- 问题
- 人们一上来就抓起最强大、最复杂的工具。
- 思路
一级一级来:更大的机器 → 缓存和 CDN → 无状态 + load balancer → 副本 → 队列 → sharding。
- 例子
- 网站慢了——先加内存、加缓存;也许永远轮不到 sharding。
- 何时使用
- 始终。只爬到你的负载逼你爬到的高度。
- 关联
- 把第 2–6 章的每个概念串在一起。
Design for Failure — 机器越多,故障越多
一盏灯泡在某个晚上很少会坏;在一座有一万盏灯的体育场里,总有几盏是灭的。规模一大,总有东西是坏的。
随着机器增多,在任何时刻总有某个东西在出故障的概率,会趋向于确定。所以扩展和可靠性是同一个项目:冗余——没有单点故障(SPOF);health checks 和 failover;以及 graceful degradation(优雅降级),让一个坏掉的部分只是降低质量,而不是让整个系统死掉。
- 问题
- 规模一大,总有某台机器是坏的。
- 思路
冗余、health checks、failover 和 graceful degradation——一个坏掉的部分不会拖垮整体。
- 例子
- 推荐服务挂了——商店展示的页面少了"你可能还喜欢"那一块,但照常卖货。
- 代价
- 备用机器要花钱;failover 必须定期演练。
- 关联
- 汇集了 Health Check、Rolling Deploy(failover)和 Decoupling。
这一切如何串起来
旅程结束后的地图:现在你看得出哪些站在同一条线上。
这门课里的概念不是互不相干的小技巧,而是一条链。测量 → 找到 bottleneck → 爬到你需要的那一级 ladder 台阶。水平扩展需要无状态服务器和 load balancer;load balancer 需要 health checks;缓存需要失效机制;队列给你带来 leveling 和 decoupling。而这一切都服务于同一个目标:一个部分出故障,不能让整个系统死掉。
- 我测量过了吗,真正的瓶颈在哪里——而不是猜的?
- 在这一级之前,有没有更便宜的台阶——更大的机器、缓存、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 和优雅降级:规模一大,总有东西是坏的。
扩展你测量出来的那个瓶颈,用能解决它的最便宜的工具,并且假定规模一大总有东西是坏的。其余的一切都是过早的。