专业书籍精读 · SRE · 第 2 章
Site Reliability Engineering · Ch 2 · Google(JC van Winkel)· 2016
你在 Google 搜一个词,零点几秒出结果。这背后不是「一台特别强的机器」,而是几万台普通机器,外加一整套软件把它们黏成一台机器。SRE 这本书的第 2 章就是带你参观这台「数据中心那么大的计算机」——它不教任何做法,只交代后面十几章所有做法的物理前提。
把它想成一座巨型工厂。以前的运维像养宠物:每台机器有名字、有脾气,谁病了谁半夜爬起来照顾。机器只有十台时这么干没问题,十万台就不行了——按概率算,每天都有一批在坏。
于是 Google 换了个办法:请一个总工头。你不再说「让 37 号机器去跑这个程序」,而是贴一张任务单——「这活要 20 份,每份给我一个 2 核 4G 的工位,别把 20 份全塞在同一排」。工头自己去排班;哪个工人病了,它把活挪给别人,你连知道都不用知道。
难点不在「机器会坏」,而在数量一大,坏就从意外变成了日常。人盯不过来,也不该盯。真正要做的是把「哪台机器」这件事整个藏起来,让人只关心「服务还在不在、快不快」。这章的价值就在于:它把这套藏法摊开给你看。
这一章开头先抠一个词。日常说的「服务器」其实是两样东西:一台铁疙瘩,和跑在它上面的一段程序。书里把它们硬性分开——铁疙瘩叫「机器」,程序叫「服务器」。一台机器上常年同时跑着好几个不相干的程序;反过来,一个服务的几十份程序散落在几十台机器上。分清这两件事,你才能听懂那句关键的话:机器死了,服务没死。
剩下的就是三样底座。一个总调度,负责把程序摆到机器上,坏了就重摆;一套层层垒起来的存储,你存一份文件,它自动摊到几千块盘上,坏几块不影响你;一张快得离谱的内部网,让「跨机器」这件事便宜到可以随便跨——这正是拆成几万台小机器还能协同干活的前提。另外还有个不起眼但要命的角色:一块所有人都信的公告板,「现在谁是主」「某个服务在哪」都写在上面,大家照着它走,就不会各执一词。
好处是:机器坏掉从事故降级成日常,人不用起夜,扩容变成改一个数字。代价也就一句话——所有人都踩在同一批底座上,那块公告板要是糊了,全体一起糊。
Google 的生产环境不是「很多台服务器」,而是一台数据中心那么大的计算机:调度、存储、网络被抽象成统一底座,人只声明「我要什么」,不关心「它落在哪台机器上」。
想看具体组件、分层图和权衡? → 切到精读版
你以为 SRE 管的是一堆机器,其实他们管的是一台数据中心大小的计算机。第 2 章不讲任何实践,只交代物理前提:Google 用 Borg(调度)、Colossus(存储)、Jupiter / B4(网络)、Chubby(协调)、Stubby(RPC)把几万台廉价机器抽象成一台可编程的机器。看懂这套底座,后面十几章讲的监控、自动化、过载防护才不是悬空的口号。
本章属于 SRE 书 Part I「导论」,紧接第 1 章之后,是全书唯一一章纯描述性的内容——作者 JC van Winkel 相当于给你发了一张厂区地图。后面几乎每一章都踩在它上面:监控(Ch 6 / Ch 10 的 Borgmon)、自动化(Ch 7 的 Borg 操作)、过载与级联失效(Ch 21 / 22 的 RPC 与负载均衡)、关键状态(Ch 23 的 Chubby)。对应到今天的现实,它就是 Kubernetes + 对象存储 + 服务网格 那一整套的原型。
这章要解决的是一件不起眼但致命的事:词对不上,后面全读不懂。同一句「把任务挪到另一台机器上」,在传统运维耳朵里是一次需要审批的变更,在 Borg 世界里是每天发生几千次的默认行为。不先统一心智模型,读者会把 Google 的做法误读成「他们胆子大」。
更深一层的理由是规模改变性质。一个集群可以有数万台机器;按业界公开的硬盘年化故障率(个位数百分比)粗算,这种规模下每天都有机器在坏。一个跑着 2000 个 task 的服务,几乎天天都有 task 被重启或被更高优先级的作业挤掉。所以 Google 的选择不是「让机器别坏」,而是把「机器」这个概念从开发者的视野里删掉——你声明要什么,不问它落在哪。不这么做的代价很直白:人会被机器数量淹没,SRE 那条「运维工作不超过 50% 时间」的红线一天都守不住。
本章开篇定义:机器是硬件(或虚拟机),服务器是实现某个服务的软件。这不是咬文嚼字——一台机器上同时跑着搜索的后端、一个日志清理任务和别人的 MapReduce;而「搜索前端」这个服务的几百个副本散在几百台机器上。两者是多对多,不是一对一。传统运维之所以难扩展,正是因为默认它俩是一对一的(一台机器 = 一个服务 = 一个名字 = 一个负责人)。
硬件本身是分层的:机架(rack)里装几十台机器,机架排成排(row),一排或几排构成一个集群,一栋数据中心里有若干集群,几栋相邻的数据中心组成园区(campus)。这个层级不是装修风格,而是故障域和带宽的边界:同机架的两台机器之间又快又便宜,但机架掉电会一起死;跨数据中心不会一起死,但延迟涨一个量级。后面所有「副本放哪」的讨论,用的都是这把尺子。
Borg 是集群操作系统——本章把它类比为 Apache Mesos。你提交的不是「在 37 号机器上启动进程」,而是一份声明:这个 job 要 20 个 task,每个 task 要 2 核 4G、8G 磁盘,别把它们放在同一个机架上。Borgmaster(控制面,本身多副本选主)算出摆法,各机器上的 Borglet 拉起二进制、监控存活;task 挂了或机器没了,Borg 在别处重建一份。
三个设计要点值得记住。其一,声明式:你描述期望状态,调度器负责收敛——今天 Kubernetes 的全部哲学都在这句里。其二,装箱与超卖:把在线服务和批处理混跑在同一批机器上,用低优先级作业填满在线服务留出的余量,机器利用率因此大幅高于「一个服务一批机器」的做法。其三,抢占:资源紧张时,高优先级作业可以把低优先级作业踢掉。代价也在这里——你的批处理作业本来就可能被随时杀掉,所以必须写成可重试、可续跑的。
还有一个容易被跳过但很关键的细节:任务不用 IP 互相寻址,而是用 BNS 名字(Borg 命名服务,形如 /bns/<集群>/<用户>/<job>/<序号>),由底层解析成当前的 IP 和端口。正因为有这层间接,task 换机器对调用方是透明的。
Google 的存储不是一个产品,而是一摞。最底下是 D(几乎每台机器上都跑的盘服务),它只负责「把这台机器的盘暴露出去」,不懂文件系统。上面一层是 Colossus(GFS 的后继),它把成千上万个 D 拼成一个集群级文件系统,负责切块、多副本 / 纠删码、坏盘替换。再往上才是我们熟悉的数据库层:Bigtable(NoSQL,按「行键 + 列 + 时间戳」寻址的稀疏大表,可到 PB 级)、Spanner(提供类 SQL 接口和全球强一致)、Blobstore(存大块不可变对象,如图片视频)。
为什么要分这么多层?因为每层只解决一类问题,上层才能假设下层不会骗它:Colossus 假设单盘会坏,于是做冗余;Bigtable 假设文件系统可靠,于是只管数据模型和分片。这也是分布式系统最常见的结构——不是一个巨系统,而是一摞彼此信任的小系统。
这摞抽象能成立,前提是机器之间说话足够便宜。Google 的数据中心网络(Jupiter 一代)用自研交换硬件加软件定义网络(SDN,控制逻辑集中在软件里而非每台交换机各自为政),把整个数据中心做成一张近似「任意两台机器都能全速互通」的网;跨数据中心则由 B4 这张软件定义广域网承担。
流量入口是 GSLB(全局软件负载均衡),它在三个层面同时做分流:地理层(DNS 把用户导到就近的数据中心)、服务层(在该地的多个前端之间分)、RPC 层(后端实例之间分)。三层是必要的——只靠 DNS,粒度太粗且缓存改不动;只靠 RPC 层,用户已经绕了半个地球才开始被均衡。
程序之间的通信一律走 Stubby(Google 内部 RPC 框架,公开版本是 gRPC),数据用 protocol buffers 编码——书里给的量级是:比 XML 小 3 到 10 倍、序列化快 20 到 100 倍。这不是锦上添花:当一次用户请求要扇出成几十次内部调用时,编解码开销会直接出现在尾延迟里。
Chubby 是基于 Paxos 的锁服务兼小体量强一致存储,干两件事:存少量所有人都必须看到同一份的信息(比如 Borgmaster 现在在哪),以及主选举——一组副本里谁当主,由 Chubby 裁决,从而避免两个主同时写。它存的数据量极小,重要性却极高:几乎所有系统都在用它,它一旦不可用,一片服务跟着不可用。
Borgmon 是监控:从每个 task 暴露的端点定期抓指标,存成时间序列,再用规则算出告警。关键设计是告警建立在可聚合的指标表达式上(「这个服务整体错误率超过 x%」),而不是逐台机器的阈值——机器随时在换,按机器告警的第一天就会被噪声淹没。这一套后来被 Prometheus 公开复刻(第 10 章专讲它)。
本章用一个玩具服务把所有零件串起来:Shakespeare——索引莎士比亚全集,支持查「某个词出现在哪些剧本里」。离线侧是一条 MapReduce 流水线读全部文本、建索引、写进 Bigtable;在线侧是前端 job 和后端 job,各自在多个集群里跑着多份副本。
用户请求的路径是:DNS 经 GSLB 落到就近数据中心的前端 task → 前端用 BNS 名字发 Stubby RPC 给后端 task → 后端查 Bigtable → 原路返回。整条链上没有任何一跳认识具体机器,每一跳都可以被 Borg 换掉而不打断服务。这就是全章想让你建立的画面。
这章最实用的一张表,是把 Google 的内部件翻译成你能真正用上的东西——几乎每个组件都有公开对应物,读懂它就等于读懂了现代基础设施的来路。
Google 内部件 ↔ 公开对应物
| Google 内部 | 公开对应物 | 它到底解决什么 |
|---|---|---|
| Borg | Kubernetes / Mesos / Nomad | 把「程序 → 机器」的摆放交给调度器,机器故障变成自动重建 |
| Colossus(GFS 后继) | HDFS / Ceph / S3 类对象存储 | 把成千上万块盘变成一个可靠的命名空间 |
| Bigtable | HBase / Cassandra | PB 级、按行键顺序分片的宽表,写入吞吐优先 |
| Spanner | CockroachDB / TiDB | 跨地域强一致 + SQL,代价是写入延迟 |
| Chubby | ZooKeeper / etcd | 少量数据的强一致存储与主选举 |
| Borgmon | Prometheus | 拉取式时序指标 + 规则告警 |
| Stubby + protobuf | gRPC + protobuf | 高效 RPC 与紧凑编码,压住扇出场景的尾延迟 |
| Blaze | Bazel | 可复现、可缓存的分布式构建 |
存储那一层的选型则是本章留给读者的第二道题:一致性和延迟不能同时要满。
存储三选一:什么场景选什么
| 选择 | 适合 | 诚实的代价 |
|---|---|---|
| Bigtable 类宽表 | 海量键值 / 时序 / 索引,读写按行键,单行操作即可满足 | 跨行没有事务;跨地域副本通常只能拿到最终一致,读到旧值要应用自己兜 |
| Spanner 类强一致库 | 钱、库存、配额这类读到旧值就是错的数据 | 跨地域提交要等多数派确认,写延迟按几十到上百毫秒计;成本更高 |
| Blobstore 类对象存储 | 图片、视频、备份等大块不可变数据 | 不能改只能覆盖;不适合小而频繁的更新 |
还有一个更大的权衡藏在 Borg 背后:共享集群 vs 专用机器。共享把利用率做上去了,也把隔离问题带了进来——邻居的批处理作业抢走内存带宽,你的 p99 就抖。Google 的答案是接受这份代价、用优先级和资源隔离去管;如果你的业务只有几十台机器,这份代价换不回同等收益,那就别抄。
这一章的价值,是把一句被说滥的话变成具体画面:「把数据中心当计算机」到底要有哪些零件。调度、集群文件系统、强一致协调、统一 RPC、统一监控——五件缺一不可。今天你在云上拼 Kubernetes + S3 + etcd + gRPC + Prometheus,拼的就是同一张图。面试里被问「你怎么设计一个能跑上万台机器的平台」,这五件套就是骨架;回答里能说清「谁负责哪一层故障」,比背组件名有用得多。
① 一句话:本章是全书的物理前提——Google 的生产环境不是一堆服务器,而是一台被软件黏成的、数据中心大小的计算机。
② 全章基石是一条定义:机器(硬件)≠ 服务器(软件),二者多对多;传统运维的一对一假设正是扩展不上去的根源。
③ 硬件层级 机器 → 机架 → 排 → 集群 → 数据中心 → 园区,本质是故障域与延迟的刻度尺。
④ Borg 是集群操作系统:声明式提交 job / task,装箱提高利用率,优先级抢占,故障自动重建;代价是低优先级作业随时可能被杀。
⑤ 存储是一摞:D → Colossus → Bigtable / Spanner / Blobstore,每层只解决一类问题,上层才敢信任下层。
⑥ 网络(Jupiter、B4)便宜到可以随便跨机器,是整套抽象成立的前提;GSLB 在地理 / 服务 / RPC 三层同时做负载均衡。
⑦ Chubby(强一致协调与主选举)与 Borgmon(时序指标 + 规则告警)体量小、依赖面极广,是可用性的上界。
⑧ 一个请求的一生:GSLB → 前端 task → Stubby RPC → 后端 task → Bigtable,全链路只认 BNS 名字、不认 IP。
⑨ 最该带走的判断:这套东西是规模逼出来的,不是品味;规模没到就照抄,买到的只有复杂度。