Google 如何运行 |《Google SRE》中文版

2,470 阅读20分钟
原文链接: zhuanlan.zhihu.com

为了让亿万用户使用到稳定可靠的服务,Google 组建了一支专业的团队负责运行这些后端服务,这些工程师有一个共同的名字:Site Reliability Engineer。了解 Google SRE 的人常说的一句话是:和你们相比,大部分公司还处于刀耕火种时代,什么时候你们这些最佳实践能够帮助帮助其他的公司呢?资深 Google SRE Chris Jones 等人联合撰写了《Google SRE: How Google runs production systems》(以下简称《Google SRE》),首次向外界解密了谷歌的生产环境。前 Google 资深 SRE ,现 Coding.net CTO 孙宇聪 先生担任了本书译者。

随着这本官方书籍的出版,Google 毫无保留的将自己十几年 SRE 的生产管理经验分享出来,业界可以近距离全方位地了解到 Google SRE 的工作内容及最佳实践,这对整个互联网行业的影响将是巨大和深远的。



本书内容包括:

  • 如何平衡可用性和成本

  • 如何制定服务的 SLO

  • 如何减少 operational 的工作

  • 分布式系统的监控

  • 自动化平台演进

  • SRE 在软件发布中的角色

本书首度系统性披露 Google SRE 之道,集 Google SRE 理论和实践之大成,可谓运维宝书。从 SRE 的视角来看 Google 生产环境会是怎样?且听本文分解。

1. 硬件

Google 大部分计算资源都存放在自主设计的数据中心中。这些数据中心拥有自己设计的供电系统、制冷系统、网络系统以及计算机硬件。和一般的服务器托管中心相比,每个数据中心的计算机硬件基本上是一致的。

为了更好地区分物理服务器和软件服务器的概念,我们在全书中都采用了以下两种说法。

  • 物理服务器(Machine): 代表具体的硬件(有时候也代表一个 VM 虚拟机)。

  • 软件服务器(Server): 代表一个对外提供服务的软件系统。

大部分情况下 Google 数据中心的硬件配置是统一的,但是也有例外情况,例如有些数据中心可能会 同时存在几种不同迭代周期产生的硬件产品,我们有时候也会修改某个现有数据中心的硬件配置。

硬件服务器上可以运行任何类型的软件服务器。Google并不会使用专门的硬件服务器运行专门软件的服务器。

例如,对 Google 来说,并不存在一个具体的硬件服务器运行我们的邮件系统,而是采用一套集群管理系统进行资源分配,它的名称为 Borg。

我们意识到 Google将软件服务器与硬件服务器区分得如此明确有些不同寻常。通常对一个软件业务来说,软件服务器和硬件服务器是紧密相连,密不可分的。

但是对 Google来说,并不是这样。具体区分的原因和细节相信读者读过下面的章节之后就会有所了解。

图 2-1 描绘了一个典型的 Google 数据中心的拓扑结构 :

  • 约 10 台硬件服务器组成了一个机柜(Rack)

  • 数台机柜组成一个机柜排(Row)。

  • 一排或多排机柜组成了一个集群(Cluster)

  • 一般来说,一个数据中心(Datacenter)包含多个集群 y 多个相邻的数据中心组成了一个园区(Campus)

<图2-1>

每个数据中心内的物理服务器都需要能够互相进行网络通信。为了解决这个问题,我们 用几百台 Google自己制造的交换机用 Clos 连接方式连接起来创建了一个非常快的虚拟网络交换机,这个交换机有几万个虚拟端口。

这个交换机的名称叫 Jupiter。在 Google 最大的一个数据中心中,Jupiter 可以提供 1.3 Pb/s的服务器交叉带宽。

Google 数据中心是由一套全球覆盖的骨干网B4连接起来的。B4 是基于 SDN 网络技术(使用 OpenFlow 标准协议)构建的,可以在一定区域内提供海量带宽,同时可以利用动态带宽管理优化网络连接,最大化平均带宽。

2. 管理物理服务器的系统软件

为了管理和控制硬件设备,我们开发了一套同样支持大规模部署的系统管理软件。硬件故障是我们用软件系统管理的一项主要问题。

因为一个集群中包括很多硬件设备,每天硬件设备的损坏量很高。平均在一年内,一个单独集群中会发生几千起硬件服务器损坏事件,几千块硬盘也会损坏。

当把这些数字乘以 Google 现有的集群数量时,这些数字就有点令人难以置信了。所以,想要将硬件故障问题与实际业务用户隔离开来。

具体业务团队运行软件服务器的时候也并不想每天处理硬件故障。每个数据中心园区都配备专门的团队负责维护硬件设备和数据中心基础设施。

2.1 管理物理服务器

Borg,如图 2-2 所示,是一个分布式集群操作系统(参见文献 [Ver15])。其与 Apache Mesos 类似, Borg 负责在集群层面管理任务的编排工作。

有些读者可能了解 Borg 的下一代,Kubernetes,开源容器化集群编排系统,Google 创立于 2014 年。 请参看 Kubernetes - Production-Grade Container Orchestration


<图2-2>

Borg 负责运行用户提交的“任务”(Job)。该任务可以是无限运行的软件服务器,或者是批量任务,例如 MapReduce。每个任务可以由一个或多个实例 (Task)组成(有时候甚至由几千个实例组成)。

通常这样组织是为了提高冗余度,而且大多数情况一个实例并不能满足整个集群的流量需求。当 Borg 启动一个任务的时候,它会为每一个实例安排一台物理服务器,并且执行具体的程序启动它。

Borg 同时会不断监控这些实例,如果发现某个实例出现异常,其会终止该实例,并且重启它,有时候会在另外一台物理服务器上重启。

因为实例与机器并没有一对一的固定对应关系,我们不能简单地用 IP 地址和端口来指代某一个具体任务实例。为了解决这个问题,我们增加了一个新的抽象层。

每当 Borg 启动某一个任务的时候,Borg 给每个具体的任务实例分配一个名字和一个编号,这个系统称之为 Borg 名称解析系统(BNS)。当其他任务实例连接到某个任务实例时,它们使用 BNS 名称连接,BNS 系统负责将这个名称转换成具体的 IP 地址和端口进行连接。

Borg 还负责将具体资源分配给每个任务。每个任务都需要在配置文件中声明它需要的具体资源 (例如: 3CPU 核心 , 2GB 内存等)。 有了这样的信息, Borg 可以将所 有的任务实例合理分配在不同的物理服务器上,以提高每个物理服务器的利用率。同时 Borg 还 关注物理服务器的故障域属性。

例如 :Borg 不会将某个任务的全部实例都运行在某一个 机柜上,因为这样一来,机柜交换机将成为整个任务的单点故障源。

如果一个任务实例资源使用超出了它的分配范围,Borg 会杀掉这个实例,并且重启它。我们发现一个缓慢的不断重启的实例要好过一个永远不重启一直泄漏资源的实例。

2.2 存储

任务实例可以利用本地硬盘存储一些临时文件,但是我们有几个集群级别的存储系统可供选择作为永久性存储。甚至我们的临时文件存储也在向集群存储模型迁移。这些存储 系统和开源的 Lustre,以及 Hadoop 文件系统(HDFS)类似。

这些存储系统负责向用户提供一套简单易用,并且可靠的集群存储服务。见图 2-3,存储系统由多层结构组成。

<图2-3>

  1. 最底层由称为 D 的服务提供(D 代表磁盘 Disk,但是 D 可以同时使用磁盘和 SSD)。D 是一个文件服务器,几乎运行在整个集群的所有物理服务器上。然而,用户具体访问某个数据时并不需要记住具体到哪个 D 服务器上去获取,这就是下一层做的事情。

  2. D 服务的上一层称之为 Colossus,Colossus 建立了一个覆盖整个集群的文件系统。 Colossus 文件系统提供传统文件系统的操作接口,同时还支持复制与加密功能。 Colossus 是 GFS 的改进版本。

  3. 构建于 Colossus 之上,有几个类似数据库的服务可供选择 :

  • Bigtable是一个 NoSQL 数据库。它可以处理高达数个 Petabytes 的数据库。Bigtable 是一个松散存储的、分布式的、有顺序的、持久 化的多维映射表。使用 Row Key、Column Key,以及 Timestamp 做索引。映 射表中的值是按原始字节存储的。Bigtable 支持“最终一致”的跨数据中心复制模型。

  • Spanner(参见文献 [Cor12])是可以提供一个 SQL 接口以及满足一致性要求的全球数据库。

  • 另外几种数据库系统,例如 Blob Store 也可用。每一种数据库都有自己的优势与劣势。

2.3 网络

Google 的网络硬件设备是由以下几种方式控制的。如前文所述,我们使用一个基于 OpenFlow 的软件定义网络(SDN),我们没有选择使用高级智能路由器连接,而是采用 了普通的非智能交换组件结合集中化(有备份的)的控制器连接方式。

该控制器负责计算网络中间的最佳路径。因此我们可以将整个集群的复杂路由计算从具体交换硬件上分离开来,从而降低成本。

网络带宽也需要被合理分配。就像 Borg 给每个任务实例分配计算资源一样,带宽控制器(Bandwidth Enforcer,BwE)负责管理所有可用带宽。

优化带宽的使用不仅仅是降低 成本。利用中心化的路由计算,我们可以解决以前在分布式路由模式下难以解决的流量迁移问题。

有些服务包括运行在不同集群上的任务,这些集群通常是遍布全球的。为了降低分布式集群的服务延迟,我们希望能够将用户指派给距离最近的、有空余容量的数据中心处理。

我们的全球负载均衡系统(GSLB)在三个层面上负责负载均衡工作 :

  • 利用地理位置信息负载均衡 DNS 请求。

  • 在用户服务层面进行复杂均衡,例如 YouTube 和 Google Maps。

  • 在远程调用(RPC)层面进行负载均衡。

每个服务的管理者在配置文件中给服务起一个名称,同时指定一系列的 BNS 地址,以及 每个 BNS 地址可用的容量(通常,容量的单位是QPS,每秒请求数量)。GSLB 会自动 将用户流量导向到合适的位置。

3. 其他系统软件

其他运行在数据中心中的几个组件也很重要。

3.1 锁服务

Chubby集群锁服务提供一个与文件系统类似的 API 用来操作锁。 Chubby 可以处理异地、跨机房级别的锁请求。Chubby 使用 Paxos 协议来获取分布式一 致性。

Chubby 是实现主实例选举(Master-Election)过程的关键组件。例如,一个服务有 5 个 冗余实例在同时运行,但是只有一个实例可以处理对外请求时,一般做法是通过 Chubby 进行自动选举,选择其中一个实例成为主实例。

Chubby 适合存放那种对一致性要求非常强的数据。例如 BNS 服务,就使用 Chubby 存放 BNS 路径与 IP:端口的对应关系。

3.2 监控与报警系统

监控与报警系统是可靠的运维服务中不可或缺的部分。因此,我们在数据中心中运行了多个 Borgmon 监控程序实例。

Borgmon 定期从监控对象抓取监控指标 (Metrics)。这些监控指标可以被用来做立即报警,也可以存储起来供以后观看。

我们主要有以下几种方式使用监控系统 :

  • 对真实问题进行报警。

  • 对比服务更新前后状态变化:新的版本是否让软件服务器运行得更快了?

  • 检查资源使用量随时间的变化情况,这个信息对合理制定资源计划很有用。

4. 软件基础设施

Google 的底层软件基础设施的设计目标是最高效地使用 Google 的硬件基础设施。我们的代码库大量采用了多线程设计方式,一个任务实例可以同时利用很多个 CPU。每个软件服务器都有一个内置的 HTTP 服务,提供一些调试信息和统计信息,供在线调试、监 控等使用。

所有的 Google 服务之前都使用远程调用(RPC)通信,称之为 Stubby。我们目前还公布了一个开源实现 ,gRPC。有的时候,一个程序的内部函数调用也会用 RPC 实现,因 为未来有需要时,可以很容易地将其重构为多个组件并行的架构。GSLB 对 RPC 的负载均衡有良好的支持。

具体参见 http://gprpc.io

通常来说,一个软件服务器从该服务器的前端(Frontend)接收 RPC 请求,同时将另外一些 RPC 发往该服务器的后端(Backend)。一般来说,前端被称为客户端(Client), 后端被称为服务端(Server)。

Protocol Buffer 是 Google RPC 的传输数据格式,通常简写为 Protobuf,与 Apache Thrift 类似。Protobuf 相比 XML 有很多优势,更为简单易用,数据大小比 XML 格式要小 3~10 倍,序列化和反序列化速度快 100 倍,并且协议更加明确。

Protocol Buffer是编程语言中性的、运行平台中性的一种数据序列化机制。

5. 研发环境

Google 非常注重研发效率,我们围绕着自己的基础设施创建了一整套研发环境。

除了一些开源项目之外(Android 和 Chrome 等),其他 Google 软件工程师全部使用同一个共享软件仓库开发。这同时也对我们的日常工作流带来一些挑战:

  • 如果一个工程师遇到了他工作的项目之外的一个基础组件的问题,他们可以直接 修改这个问题,向管理者提交一份改动申请(Change List, CL),等待代码评审, 最后直接提交。

  • 任何对代码的改动也需要代码评审。

在软件编译过程中,编译软件会向运行在数据中心的编译服务器发送请求。

Google 编译软件可以通过并行化机制,处理超大型的编译请求。这套技术架构体系同时也用来进行持续测试。每当一个 CL 被提交时,所有被这个 CL 直接或间接影响到的测试都会运行一次。

如果测试框架检测到一个 CL破坏了其他某个系统的正常工作,测试框架会向提交者发送通知。甚至有些项目组在实践自动部署机制:提交一个新版本,测试通过后, 将直接部署于生产环境。

6. 莎士比亚搜索 :一个示范服务

为了更好地说明一个服务是怎么利用各种基础设施,以及在 Google 生产环境中部署的,我们在这里提供一个假想的莎士比亚搜索服务。假设这个服务的作用是在所有莎士比亚的文献中搜索给定词语。

整个系统可以分为两大部分 :

  • 批处理部分。给全部莎士比亚文献创建索引,同时将索引写入一个 Bigtable 中。这项任务基本只需运行一次。(如果突然发现了新的莎士比亚文献,那就需要再运行一次。)

  • 一个应用程序前端服务器(Frontend)用以接收处理用户请求。该服务器是一直在线的,因为全球范围的用户都需要使用我们的服务。

批处理部分可以用 MapReduce 框架完成,三个阶段的实现分别如下所示。

  • Mapping 阶段:该程序遍历所有的莎士比亚的文字,将其分成具体的单词。这项 任务可以利用多实例并行加速。

  • Shuffle 阶段:该程序将上一阶产生的所有单词和位置等进行排序。

  • Reduce 阶段:将上一阶段产生的单词或位置等按单词合并,产生一个新的单词或位置列表。

最后,程序将每一个的单词或位置列表写入到 Bigtable 中,Row Key 就是这个单词。

6.1 用户请求的处理过程

图 2-4 显示了一个用户请求的处理全过程。首先,用户使用浏览器访问https://shakespeare.google.com。为了获得 IP 地址,用户设备需要请求 DNS 服务器 (1)。该 DNS 请求最后会到达 Google 的 DNS 服务器。Google DNS 服务器会请求 GSLB 系统。 GSLB通过全局流量负载信息,决定使用哪个 IP 地址回复用户。


<图2-4>

用户浏览器利用获得的 IP 地址连接到 HTTP 服务器,这个服务器(Google 前端服务器 GFE)负责终结 TCP 连接,并且反向代理请求到真正的服务器上(2)。GFE 从配置文 件中找到该请求对应的后端服务(配置文件中包括所有 Google 服务,例如 web search、 maps 以及本例中的 Shakespeare)。GFE 再次咨询 GSLB 系统,获得一个 GSLB 分配的 目前可用的 Shakespeare 服务器地址,向其发送一个 RPC 请求(3)。

Shakespeare 前端服务器分析接收到的请求,构建出一个具体查询的 Protobuf 请求。这时 Shakespeare 前端服务器需要联系后端服务器(Backend)来做具体查询。前端服务器也需要联系 GSLB 服务,获取目前可用的(同时符合负载均衡条件的)后端服务器的 BNS 地址(4)。Shakespeare 后端服务器随后请求 Bigtable 服务器来获得最终查询结果(5)。

最终结果被写入一个 Protobuf 结构中,返回给 Shakespeare 后端服务器,后端服务器将其回复给 Shakespeare 前端服务器,前端服务器最终根据这个数据结构构建 HTML 回复给最终用户。

上述这些连锁事件其实一共耗时几百毫秒。因为一个请求涉及很多组件,这些组件都必须相当可靠才行。例如,GSLB 服务如果出现问题,将会造成严重故障。但是 Google 依靠严格的测试和灰度发布流程,以及很多主动优雅降级的措施,使得我们可以为用户提供一个非常稳定的服务。

由于 Google 的可靠性举世闻名,人们经常通过访问 http://www.google.com 来验证他们的网络服务是否正常。

6.2 任务和数据的组织

假设 :压力测试的结果显示,我们的服务器可以每秒处理大概 100 个请求(100 QPS)。通过对用户进行的调查显示,我们预计峰值流量会达到 3470 QPS,为了处理这些流量,我们至少需要 35 个任务实例。但是,由于以下几个考量,我们最终决定至少采用 37 个实例,也就是 N+2 模式 :

  • 在更新过程中,有一个任务实例将会短暂不可用,只有 36 个实例可提供服务。

  • 如果另外一个物理服务器同时也出现问题,那么另外一个任务实例也受到影响,只剩 35个实例可以对外服务,刚刚可以满足峰值要求。

我们假设同时出现两个任务实例不可用的情况的可能性很低,足以忽略不计。但是单点故障源,例如供电设施问题,或者机柜交换机问题,可能会影响这里的假设。

假设,对用户流量的进一步观察显示,我们的峰值流量其实是来自全球的。北美 1430 QPS,南美洲 290 QPS, 欧洲和非洲 1400 QPS, 亚洲及澳州共 350 QPS。

为了更好地服务用户,我们需要将服务分别部署在美国、南美洲、欧洲和亚洲。在南美洲,我们选择使用只部署 4 个实例(而不是 5 个),将冗余度降低为 N+1。这样做的原因是我们选择在极端情况下牺牲一些用户体验,以便降低成本。

因为当容量不足时,GSLB 会将南美洲的用户流量导向其他可用的数据中心,可以节省大概 20% 的硬件资源。在有条件的地方,我们还会将任务实例分散在不同的集群中(Cluster),以便更好地提升可靠度。

因为 Shakespeare 后端服务器需要连接 Bigtable 服务读取数据,我们同时也需要合理地 安排数据存储。亚洲的后端服务器尝试访问部署在美国的Bigtable 会面临延迟问题。所以我们在每个地理区域都存放了 Bigtable 的副本。

利用 Bigtable 的复制功能,我们可以同时达到两个目的 :

  • 当 Bigtable 服务出现问题时,还可以利用副本提供服务。

  • 任务实例可以利用本地 Bigtable 加速数据访问。

虽然 Bigtable 仅仅提供最终一致性保障(Eventual Consistency),但是由于我们的数据更新并不频繁,所以对我们来说并不是问题。

本书《Google SRE》中文版即将在大陆发售
该节选译文版权归译者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

推荐阅读本书译者孙宇聪的其他文章:

- 来自 Google 的高可用架构理念与实践

- Coding 孙宇聪:《人,技术与流程》

- Docker 技术与 Coding.net 技术架构的变迁