分布式 build

报告问题 查看来源 每晚 · 7.2。 · 7.1敬上 · 7.0 · 6.5 条 · 6.4

如果您的代码库非常庞大,依赖项链可能会变得非常深。 即使是简单的二进制文件,通常也依赖于成千上万个构建目标。在 这么大的规模,很难在合理时间内完成一次构建。 在一台机器上运行所需的时间:没有构建系统可以规避 物理定律。只有这样,才能实现这个目标 它具有支持分布式构建的构建系统,其中, 系统完成的工作分布在任意且可扩展的 机器数量假设我们已将系统的工作分解成足够小的工作 (稍后会详细介绍相关内容),这样我们就能够完成任何 以达到我们愿意付费的速度。这种可伸缩性是制胜法宝 我们一直致力于通过定义基于工件的构建系统来实现这一目标。

远程缓存

最简单的分布式构建类型是只使用远程 缓存,如图 1 所示。

具有远程缓存的分布式构建

图 1. 显示远程缓存的分布式构建

执行构建的每个系统,包括开发者工作站和 持续集成系统、共享对通用远程缓存的引用 服务。此服务可能是一个快速的本地短期存储系统,例如 Redis 或 Google Cloud Storage 等云服务。每当用户需要 构建工件,无论是直接构建工件还是作为依赖项构建,系统首先会检查 检查远程缓存中是否已存在该制品。如果是,它 您可以下载工件而无需构建工件否则,系统会构建 并将结果上传回缓存。这意味着 不经常变化的低级别依赖项,只需构建一次即可共享 而无需由每位用户重新构建Google 的许多员工 从缓存提供,而不是从头构建, 降低运行构建系统的成本

要使远程缓存系统正常工作,构建系统必须保证 完全可以重现也就是说,对于任何构建目标, 确定目标的一组输入,以使同一组输入 在任何机器上生成完全相同的输出。这是 确保下载工件的结果与结果相同 自行构建模型请注意,这要求缓存中的每个工件 都与其目标及其输入的哈希值相对应,这样, 工程师可以同时对同一目标进行不同的修改 远程缓存将存储生成的所有工件, 对它们进行适当处理,而不会发生冲突。

当然,若要从远程缓存中获得任何好处,下载 工件需要比构建它更快。实际情况并非始终如此 尤其是在缓存服务器离进行构建的机器较远时。Google 的 网络和构建系统经过精心调优, 结果。

远程执行

远程缓存不是真正的分布式构建。如果缓存丢失,或者 进行低级更改(需要重新构建所有内容),您仍然需要 在您的机器本地执行整个构建。真正的目标是 远程执行,此时构建的实际工作可以分散 在任意数量的工作器上运行图 2 描绘了一个远程执行系统。

远程执行系统

图 2. 远程执行系统

在每个用户的计算机(其中用户要么是人类)上运行的构建工具, 工程师或自动化构建系统)将请求发送到中央构建主实例。 构建主实例将请求分成其组件操作和时间表 通过可扩缩的工作器池执行这些操作。每个工作器 通过用户指定的输入来执行所要求的操作, 写出生成的工件这些工件会与其他 机器执行需要它们的操作,直到最终输出 生成并发送给用户

要实施此类系统,最棘手的部分是管理 在工作器、主实例和用户本地机器之间分配工作器可能会 依赖于其他 worker 生成的中间工件,以及最终输出 需要被发送回用户的本地计算机为此,我们可以 让每个工作器写入到之前所述的分布式缓存之上 读取其结果并从缓存中读取其依赖项。主代码块 工作器会一直执行,直到其依赖的所有任务都完成 他们将能够从缓存中读取输入。最终的结果是 以便本地计算机进行下载请注意,我们还需要 使用单独的方法导出用户源代码树中的本地更改 工作器可以在构建之前应用这些更改

为了实现这一目的,基于工件的构建系统的所有部分描述的 需要整合在一起构建环境必须 使员工无需人工干预即可启动工作。构建 过程本身必须是完全独立的,因为每个步骤可能 在其他机器上执行输出必须是完全确定性的, 确保每个工作器可以信任它从其他工作器接收的结果。此类 但基于任务的系统很难提供保证, 基于云技术构建可靠的远程执行系统几乎不可能 一个。

Google 的分布式 build

自 2008 年以来,Google 一直在使用分布式构建系统,该系统采用 远程缓存和远程执行,如图 3 所示。

高级构建系统

图 3. Google 的分布式构建系统

Google 的远程缓存称为 ObjFS。它由一个后端组成 在 Bigtable 中构建输出,这些输出分布在我们整个生产舰队中 以及一个名为 objfsd 的前端 FUSE 守护程序,该守护程序在每个开发者的 虚拟机。通过 FUSE 守护程序,工程师可以像浏览 build 输出一样浏览 build 输出 是存储在工作站上的普通文件,但文件内容 仅对由 用户。按需传送文件内容可大大减少网络和磁盘容量 而系统的构建速度是存储容量的两倍。 开发者本地磁盘上的所有构建输出

Google 的远程执行系统名为 Forge。Blaze 中的 Forge 客户端 (Bazel 的内部等效项)调用 分销商将每个操作的请求发送到 称为“调度器”调度器会维护操作缓存 结果,以便在操作已触发时立即返回响应 由系统的任何其他用户创建。否则,它会将操作放入 队列。庞大的执行器作业池会持续读取此队列中的操作, 执行这些命令,并将结果直接存储在 ObjFS Bigtable 中。这些 结果可供执行程序用于将来的操作,或可供下载 由最终用户通过 objfsd 提交

最终的结果是系统可以进行扩展以高效支持所有版本 Google 执行的相关操作。Google 产品的规模超级大: 运行数百万个构建,执行数百万个测试用例并生成 PB 级数据, 每天数十亿行源代码的构建输出。不仅 这样的系统让我们的工程师能够快速构建复杂的代码库, 我们实施了大量自动化工具和系统,这些工具和系统依赖于我们的 build。