这一章在做什么
一个应用除了「要做什么」,还有一串很少写下来的要求:要快、要稳、扛得住增长、改得动。这一章把其中四项说成能测、能讨论的东西。开头用一个推送新内容的案例算账:结果是读的时候现算,还是写的时候先算好存起来。接着讲性能怎么描述(响应时间是一个分布,要看百分位和尾巴)、可靠性(一个部件出错不等于整个系统停摆)、可扩展性(先把负载说清楚)和可维护性。每一项都有代价,要拿数字去权衡。
读之前:先预测
第 1 章讲架构上的几对取舍:事务型和分析型、云服务和自建、分布式和单机。这一章给全书准备一套说话的词:响应时间、百分位、故障与失效、负载、可扩展。后面处处用到它们:第 6 章的复制和第 7 章的分片,分别回答「坏了怎么办」和「装不下、扛不住怎么办」;第 9 章细讲这一章一笔带过的超时和不可预测的延迟;第 12 章回到这里提到的「恰好一次」。
「要快」「要稳」「以后用户多了也得扛住」「要好改」,人人都这么要求。怎样把这些话说成能测量、能拿来做取舍的需求?说清楚之后,各自的代价落在哪里?
- 把查询结果预先算好存起来,省的是哪边的活、多出来的是哪边的活?碰上极端的用户怎么办?§1.2
- 为什么不看平均响应时间,而要看 p99、p999?一个请求要调很多后端时,尾巴为什么变重?§2.2§2.3
- 故障和失效差在哪?为什么软件故障常常比硬件故障更难对付?§3§3.2
- 说「X 可扩展」为什么没意义?讨论扩展时该问什么?§4
这一章的骨架
这一章先讲一个案例(§1),然后四项需求各占一节:性能(§2)、可靠性(§3)、可扩展性(§4)、可维护性(§5)。案例里的数字在性能和扩展两节被反复拿来举例,读后面时可以翻回去对。容易乱的是 §2 和 §3:§2 里响应时间、延迟、服务时间三个词有各自的意思;§3 开头那对 fault 和 failure,后面整节都靠它。
边读边填:每行一个问题,读到答案就写一两句。只存在你这台设备的浏览器里,别人看不到。
| 要找的 | 带着这个问题读,找到了就写下来 |
|---|---|
| 扇出的代价 | 先推好的做法,代价在什么时候付?碰上粉丝极多和关注极多的两种用户,各怎么处理?§1.2 |
| 过载 | 吞吐接近上限时响应时间为什么急剧变坏?过载的系统为什么可能回不来?§2 |
| 在哪里量 | 为什么响应时间要在客户端量,而不是看服务端记的处理时间?§2.1 |
| 百分位怎么合 | 几台机器的百分位能不能取平均?正确的合并办法是什么?§2.3 |
| 冗余的前提 | 冗余在什么条件下最有效?哪一类故障让它失灵?§3.2 |
| 人为失误 | 作者为什么说把事故归咎于人没有用?换成什么做法?§3.3 |
| 规划多远 | 扩展的规划该提前几步?书里给了哪两条通用原则?§4.3 |
读完了?对照我找到的
- 扇出的代价:每发一条都要写进每个粉丝的时间线;关注太多的人可以丢掉一部分写入,粉丝极多的名人单独存,读的时候再并进来。
- 过载:请求要排队等前面的处理完;超时重试让请求更多,形成重试风暴,负载降下来了系统也可能卡在过载状态(亚稳态失效)。
- 在哪里量:排队时间不算在服务时间里,几个慢请求堵在前面,后面快的请求在客户端看来也慢。
- 百分位怎么合:取平均在数学上没有意义;要把各台的直方图加起来再算。
- 冗余的前提:各部件的故障彼此独立;同一份软件里的同一个 bug 会在所有节点上同时发作。
- 人为失误:它是社会技术系统出了问题的症状;做法是无责复盘、测试、快速回滚、逐步发布、好的监控和界面。
- 规划多远:一般不超过一个数量级;把系统拆成能独立运行的小部件,以及不要比需要的更复杂。
分节地图
全章约 9100 词(不算代码和图注),建议分三次读,每次一个主题:第一次是案例和性能,第二次是可靠性,第三次是可扩展性和可维护性。第二次的 §3.2 最长,大半是一条条故障的例子,可以读得快些。
- 第一次坐下§0–§2.3 · 约 3300 词 · 一个案例,然后是怎么描述性能
- If you are building an application…约 300 词开篇功能需求和非功能需求的区别;这一章挑了哪四项来讲。
- Case Study: Social Network Home Timelines约 100 词案例:社交网络的首页时间线案例的设定:每天发多少条、平均关注多少人。后面的计算都从这几个数出发。
- Representing Users, Posts, and Follows约 300 词怎么表示用户、帖子和关注最直接的做法是读的时候现查现拼。算一算它每秒要做多少次查找。
- Materializing and Updating Timelines约 600 词把时间线物化并保持更新反过来,写的时候先推好。工作量挪到了哪里,两种极端用户怎么处理。
- Describing Performance约 500 词描述性能吞吐和响应时间,以及两者为什么相互牵连。方框里讲过载后回不来的系统。
- Latency and Response Time约 400 词延迟和响应时间几个容易混用的词在本书里各指什么;响应时间为什么时快时慢。
- Average, Median, and Percentiles约 700 词平均值、中位数和百分位数为什么看分布不看平均;高百分位对谁要紧。方框里是几份常被引用的研究,留意作者的保留。
- Use of Response Time Metrics约 400 词响应时间指标怎么用一次请求调很多后端时尾巴怎么放大;SLO 和 SLA;方框讲百分位怎么算、怎么合。
- 第二次坐下§3–§3.3 · 约 2600 词 · 可靠性:部件会坏,系统要撑住
- Reliability and Fault Tolerance约 300 词可靠性和容错「可靠」的几条直观期待;故障和失效这对词怎么分。
- Fault Tolerance约 400 词容错容错总有范围;为什么有人故意制造故障。
- Hardware and Software Faults约 1100 词硬件故障和软件故障一组硬件故障率,冗余能做什么;软件故障为什么更难缠。全章最长的一节。
- Humans and Reliability约 800 词人和可靠性怎么看待人为失误,组织该做什么。方框讲可靠性对普通应用有多要紧。
- 第三次坐下§4–§6 · 约 3200 词 · 可扩展性和可维护性,加小结
- Scalability约 300 词可扩展性什么时候该操心扩展,什么时候不该;讨论扩展该问哪几个问题。
- Understanding Load约 400 词理解负载用什么数描述负载;负载涨了,从哪两个角度看系统的反应。
- Shared-Memory, Shared-Disk, and Shared-Nothing Architectures约 400 词共享内存、共享磁盘和无共享架构加资源的三种办法,各自卡在哪里。
- Principles for Scalability约 300 词可扩展的原则为什么没有通用的可扩展架构;两条普遍适用的原则。
- Maintainability约 300 词可维护性软件的钱大多花在上线以后;可维护性拆成三个方面。
- Operability: Making Life Easy for Operations约 300 词可运维性:让运维省心自动化的两面;数据系统能替运维做哪些事。
- Simplicity: Managing Complexity约 500 词简单:管住复杂度「简单」为什么难有客观标准;抽象是对付复杂度的主要工具。
- Evolvability: Making Change Easy约 300 词可演化性:让改动容易需求总在变;什么让大系统难改。
- Summary约 300 词小结全章回顾。先自己回忆一遍再读它。
先用中文过一遍
先算好存起来:物化和扇出
书里用社交网络的首页时间线来算这笔账。这里换个场景:外卖 app 里有一页「关注」,列出你收藏的店铺最近发的动态(上新、满减)。第一种做法是读的时候现拼:你一打开,就去查你收藏的四十家店各自最近发了什么,按时间归并,取最新的几十条。发动态很便宜,打开页面却要查四十次;在线的人一多、刷得一勤,查询量是「打开次数 × 收藏数」。
第二种做法是写的时候先推:每个用户有一个收件箱,店铺每发一条,就查出收藏它的人,往每个人的收件箱里放一份。打开页面只读自己的收件箱,一次就够。把查询的结果预先算好存起来,叫物化(materialization),收件箱就是一种物化视图。代价挪到了写这边:一条动态要写的份数等于收藏它的人数,一个请求变成多少个下游请求,这个倍数叫扇出(fan-out)。多数店铺收藏的人不多,没问题;碰上几百万人收藏的连锁品牌,发一条就要写几百万次。读 §1.2 时留意书里对两种极端情况的处理为什么不一样。
响应时间:客户端看到的那个数
描述性能,最常用两个数:吞吐量(每秒处理多少请求)和响应时间(从发出请求到拿到结果)。两者相互牵连:机器越忙,新来的请求越可能要排队,等前面的处理完;吞吐接近机器的上限时,排队时间会急剧变长。
响应时间可以拆开看:请求在网络上走的时间、在服务器上排队的时间、真正被处理的时间(服务时间)、结果传回来的时间。服务端的日志往往只记服务时间。下图三个请求几乎同时到达一台一次只能处理一个请求的服务器:A 碰上了一个慢操作,B 和 C 自己都很快,却只能排在 A 后面。这叫队头阻塞(head-of-line blocking)。服务端的日志说 B 用了 20 毫秒,用户等了 240 毫秒。
看分布,不看平均:百分位和尾巴的放大
同一个请求反复发,每次的响应时间都不一样,所以它是一个分布,不是一个数。把一段时间里的响应时间从快到慢排好,正中间那个是中位数(p50):一半请求比它快。排在 99% 位置的是 p99:一百个请求里只有一个比它慢。p99、p999 这些高百分位叫尾延迟(tail latency)。平均值把少数特别慢的请求摊薄了,看不出有多少人真的等了那么久。
一个请求要调很多后端时,尾巴会被放大。比如一次搜索要并行查 100 个分片,等全部返回才出结果,整次搜索的时间就是最慢那个分片的时间。每个分片只有 1% 的调用慢,100 个分片里至少碰上一个慢的概率却是 1 − 0.99¹⁰⁰,约 63%。下图是这个概率怎么随分片数涨;下面的模拟器可以自己调参数。
故障不等于失效
可靠性粗略地说是「出了问题还能正常工作」。书里把「问题」分成两层:系统的某个部件不正常了,叫故障(fault);整个系统不再提供该提供的服务,叫失效(failure)。同一件事,站在不同的层次看法不同:一块盘坏了,对这块盘自己是失效;对一个把数据存了三份的存储系统,只是一个故障。能扛住某些故障、不让它升级成整体失效,叫容错(fault tolerance);扛不住、一坏就全停的那个部件,叫单点故障。
容错的老办法是冗余:多一块盘、多一路电源、多一台机器。冗余管用有一个前提:备份和原件不会一起坏。读 §3.2 时,拿这个前提去对照书里列的硬件故障和软件故障,再看 §3.3 为什么把人为失误放到组织的层面去谈。
这一章点名的系统
| 英文 | 中文 | 一句话 |
|---|---|---|
| nonfunctional requirements | 非功能需求 | 不是「做什么」,而是做得多快、多稳、多好改这类要求(§0)。 |
| fan-out | 扇出 | 一个请求引出多少个下游请求,这个倍数(§1.2)。 |
| materialized view | 物化视图 | 预先算好并随写入更新的查询结果,读快、写贵(§1.2)。 |
| throughput | 吞吐量 | 每秒处理多少请求或多少数据(§2)。 |
| metastable failure | 亚稳态失效 | 过载引发重试,重试加重过载,负载降了系统也回不来(§2)。 |
| response time | 响应时间 | 客户端从发出请求到收到结果的全部时间(§2.1)。 |
| latency | 延迟 | 请求没在被处理的那段时间的统称,比如在网络上走(§2.1)。 |
| head-of-line blocking | 队头阻塞 | 几个慢请求堵在前面,后面快的请求也只能等(§2.1)。 |
| percentile | 百分位数 | 把响应时间排序后某个位置上的值,p50 即中位数(§2.2)。 |
| tail latency | 尾延迟 | p99、p999 这类高百分位的响应时间(§2.2)。 |
| SLO / SLA | 服务水平目标 / 协议 | SLO 定目标(如 p99 低于 1 秒),SLA 是没达到时怎么办的合同(§2.3)。 |
| fault / failure | 故障 / 失效 | 一个部件出错,和整个系统不再提供服务(§3)。 |
| fault tolerance | 容错 | 某些故障发生时系统仍然提供服务(§3.1)。 |
| single point of failure (SPOF) | 单点故障 | 它一出故障,整个系统就失效的那个部件(§3.1)。 |
| shared-nothing architecture | 无共享架构 | 多台各有 CPU、内存、磁盘的机器,靠网络协调;也叫横向扩展(§4.2)。 |
动手:模拟器
先猜:一次搜索并行查 10 个分片,每个分片 1% 的调用要 400 毫秒以上,其余几十毫秒。整次搜索的 p99 是快还是慢?分片加到 100 个,中位数呢?猜好了再选参数验证。
扇出与尾延迟
一次搜索并行查 N 个分片,等最慢的那个返回才算完。每次跑 1000 次搜索,看整次搜索的 p50、p99 怎么随分片数和慢请求比例变化。先保持 1%,把分片数从 1 调到 100。
- 慢调用比例 1%,分片数依次选 1、10、100,看「碰上慢分片的搜索」和搜索的 p50、p99。哪一步 p99 先变慢,哪一步连 p50 也慢了?
- 分片数 100,把慢调用比例降到 0.1%。中位数回来了,p99 呢?要让 100 个分片的搜索 p99 也快,每个分片得在哪个百分位上快?
- 分片数 1、慢调用比例 1%,连点几次「换一批」,看搜索的 p99 怎么跳。1000 个样本里,决定 p99 的只有最慢的十来个。
词与短语
词条按在书里出现的顺序排,按节分组;读一节之前先把这一节的过一遍,读的时候就不用停。斜体小字是原句里连续的几个词,在电子书里搜它就能找到那一句。标着「每个字都认识」的,是字面意思会把你带偏的。行话(compaction、quorum 这类)不在这里,在上面的术语表里。
§0 If you are building an application…
§1.1 Representing Users, Posts, and Follows
§1.2 Materializing and Updating Timelines
§2 Describing Performance
§2.1 Latency and Response Time
§2.2 Average, Median, and Percentiles
§2.3 Use of Response Time Metrics
§3.1 Fault Tolerance
§3.2 Hardware and Software Faults
§3.3 Humans and Reliability
§4 Scalability
§4.1 Understanding Load
§4.3 Principles for Scalability
§5 Maintainability
§5.1 Operability: Making Life Easy for Operations
§5.2 Simplicity: Managing Complexity
§5.3 Evolvability: Making Change Easy
下面是读完再看的:你的预测对不对、几道换成你来选的判断题、值得细读的句子和理解检查。先看了,读书时就只是在找答案。
读完:回到你的预测
答案是第三项(§2.3)。整次搜索要等所有分片返回,只要有一个慢,整次就慢;100 个分片全都快的概率是 0.99 的 100 次方,约 37%,所以约 63% 的搜索会慢。书里把这叫尾延迟放大,并没有给这组数字,这是本站按同样的道理算的。慢的搜索超过一半,意味着连中位数都落在 1 秒以上:每个分片的 p99 很好看,整页的 p50 已经不能看了。想一想:每个分片的慢调用降到 0.1%,答案会变成多少?中位数和 p99 各是快还是慢?
答案是第四项(§2.3 的方框)。书里说把百分位取平均在数学上没有意义,正确的合并办法是把各台的直方图加起来再算。原因是百分位看的是排在某个位置的那个值,不是一个能加减的量:整体的 p99 是两台合起来排在 99% 处的那个值,它取决于两台各自的尾巴长什么样。如果第一台最慢的 1% 都只比 100 毫秒多一点,第二台除了最慢的 1% 也都在 100 毫秒以内,整体 p99 接近 100 毫秒;如果它们都是 5 秒,整体 p99 正好是 900 毫秒。所以 900 毫秒只是上限,取平均的 500 毫秒则可能离真值很远。想一想:如果两台的流量相差十倍,只看那台大流量机器的 p99 够不够?
判断题:换成你来选
场景都是本站编的。先选,再看解释;解释里写了对应的节号,拿不准就回原书看那一节。
场景:一个企业内部的通讯工具,员工打开首屏要看到所订阅频道的最新公告。现在每条公告都写进每个订阅者的收件箱。部门公告一般发给几十人,相安无事;但每次发全员公告(8 万人),投递队列会积压半小时,很多人当天很晚才看到。
这正是书里处理粉丝极多的发送方的办法:大扇出的那一类单独存,读的时候合并,其余的照旧推。全改成现拼,每个人每次打开都要查很多频道,读的代价全回来了。加机器能缓解,但要为每周几次的尖峰常备大量资源。丢掉一部分写入,书里只允许用在「关注太多、反正看不完」的读者那一侧,全员公告每个人都必须收到。
场景:一个对外的支付接口,监控看板只显示每分钟的平均响应时间,一直稳定在 80 毫秒左右。交易量最大的几家商户却反复投诉「经常卡好几秒」。
平均值把少数很慢的请求摊薄了,中位数更是只说典型情况,两者都看不到尾巴。书里举的亚马逊的理由正好对得上:最慢的请求往往来自数据最多的客户,也就是最有价值的客户。缩短统计间隔也许能看到一点波动,但仍然不知道有多少人、哪些人在等。商户网络的说法要在客户端测过才能下结论。
场景:库存服务因为数据库切换卡了 3 分钟。数据库早就好了,库存服务却一直大量超时,直到把它整个重启才恢复。调用它的客户端超时设为 1 秒,超时后立刻重试,最多重试 5 次。
这是书里方框讲的重试风暴:卡顿让请求排长队、纷纷超时,立刻重试又把请求量放大好几倍,起因消失了系统仍卡在过载状态(亚稳态失效)。退避、抖动和熔断直接切断这个反馈回路。超时更短、重试更多都只会让风暴更猛。加机器提高了上限,但回路还在,下一次更大的波动照样能把它推进去。
场景:一个服务在三个可用区各部署 4 台机器,设计目标是扛住整个可用区宕机。某天午夜,全部 12 台同时卡死:服务依赖的一个日期处理库碰到了一个从没测过的日期。
冗余的前提是故障彼此独立。硬件故障大体满足这个前提,软件故障恰恰相反:每台机器跑同一份代码,触发条件一到,所有副本一起坏。书里的例子是闰秒和固件在固定运行时长后失效。多加机器、跨地域部署都在复制同一个 bug;硬件老化不会让 12 台在同一秒卡死。
场景:一个上线三个月的小型记账 SaaS,日活 3000,一台 PostgreSQL 的负载不到 10%。技术负责人提议现在就拆成 20 个微服务、做分片、上自动扩缩容,理由是「将来到一亿用户也不用改」。
书里的看法:产品还小、需求还在变时,首要目标是简单、灵活;为假想的规模投入,好的情况是白费力气,坏的情况是把你锁死在不灵活的设计里。架构一般只值得提前规划一个数量级。分片和换架构都是在没有瓶颈时提前付复杂度的账;先弄清负载在哪一维增长,才知道该扩哪里。
和本站其他书对照
值得停下来的句子
What we call “human error” is not really the cause of an incident, but rather a symptom of a problem with the sociotechnical system— §3.3。本站译:我们所说的「人为失误」并不真是事故的原因,而是社会技术系统出了问题的症状。
它把追问的方向倒了过来:事故复盘问到「谁按错了按钮」就停,下一次换一个人照样会按错。作者接着把原因落到组织的优先级上(功能和测试二选一时,很多组织选功能),所以 §3.3 的办法几乎都不是技术手段,而是无责复盘、改激励、改投入。这一节让可靠性从硬件和代码延伸到了人。
Scalability is not a one-dimensional label—it is meaningless to say “X is scalable” or “Y doesn’t scale.”— §4。本站译:可扩展性不是一个一维的标签,说「X 可扩展」或「Y 扩展不了」没有意义。
选型争论里常听到「这个库扩展性好」。这句话要求你接着问:沿着哪个方向增长?加什么资源?到哪一步会碰到墙?它也解释了 §4.1 为什么先要把负载描述清楚,负载的形状不同,「能扩展」的意思就不同。
检查一下理解
1.(§1.2)写的时候先推好,为什么读能变快?代价落在哪里?
读的时候不用再查很多来源、再归并,直接取预先算好的结果。代价是每次写都要更新所有相关的结果,写入量按扇出的倍数放大;发送方的受众越大,一次写越贵。
2.(§2)吞吐接近机器的上限时,响应时间为什么急剧变长?
因为排队。机器越忙,新到的请求越可能碰上 CPU 正在处理别的请求,只能等着;接近上限时,等待的时间增长得非常快。
3.(§2.1)为什么要在客户端测响应时间?
排队时间不算在服务时间里。服务端只记自己处理用了多久,看不到请求在队列里等的那一段;几个慢请求堵在前面时,后面的请求处理很快,用户却等了很久。
4.(§2.2)p95 等于 1.5 秒是什么意思?为什么平均值替代不了它?
一百个请求里有 95 个在 1.5 秒以内,5 个要 1.5 秒或更久。平均值是全部加起来再除,少数很慢的请求被摊薄,看不出有多少用户真的等了那么久。
5.(§2.3)一个页面要并行调很多后端,为什么后端的高百分位尤其要紧?
页面要等最慢的那个调用。哪怕每个后端只有很小比例的慢调用,调用的个数一多,至少碰上一个慢调用的概率就大了,慢页面的比例随之上升。
6.(§3)一块硬盘坏了,算故障还是失效?
看站在哪一层。对这块盘自己是失效;如果系统里有别的盘存着副本,对整个系统只是一个故障,系统可以容忍它。只有一块盘的系统,这块盘坏了系统也就失效了。
7.(§3.1)为什么有人故意让进程随机挂掉?
很多严重的 bug 出在错误处理上。故意注入故障,让容错机制经常真的跑起来、被检验,等故障自然发生时才更有把握它能正确处理。
8.(§4.1)什么叫线性扩展?为什么实际成本常常涨得更快?
资源翻倍就能在性能不变的情况下扛住翻倍的负载。实际上常常做不到,原因很多,比如数据多了,同样大小的写请求要做的工作也更多。
用一句话说这一章(写给一个没读过的朋友):
精彩回顾
一句话:快、稳、能扩展、好维护,都要先说成能测的数字,才能看清代价落在哪里。
读的时候现拼,还是写的时候先推好:总工作量未必少了,只是挪到了另一头。受众极大的发送方要单独处理。
§1.2吞吐接近机器的极限时,响应时间不是慢慢变长,而是陡然变长。过载时立刻重试,会把系统推进回不来的状态。
§2排队时间不算在服务时间里。几个慢请求堵在前面,后面的快请求在用户那里一样慢,所以响应时间要在客户端量。
§2.1亚马逊按 p999 定内部服务的要求:最慢的请求往往来自数据最多、买得最多的客户。但再往上的 p9999 就不划算了。
§2.2并行调 100 个后端,每个 1% 慢,约六成的请求会碰上慢的那个。后端的尾巴,就是页面的身子。
§2.3硬件故障大体独立,冗余管用;软件故障高度相关,所有节点跑同一份代码,一个条件就能让它们一起倒下。
§3.2能扛住当前负载的架构,多半扛不住十倍。快速增长的服务每涨一个数量级就得重新想,再远的不值得提前规划。
§4.3