连载:谷歌为什么使用MONOREPO?(上)
原文作者:Rachel Potvin, Josh Levenberg
原文链接:Why Google Stores Billions of Lines of Code in a Single Repository
发表时间:July 2016
0
Google已经展示出:一个拥有10亿个文件,3500万次提交和数以万计开发人员的单仓代码库源代码管理和扩展能力。
这种模型的优点在于:统一的版本控制、广泛的代码共享、简化的依赖管理、原子化的更改、大规模的重构、跨团队的协作、灵活的代码所有权和代码可见性。
缺点在于:必须为创建以及维护代码健康而创建一些相应的工具平台,以及潜在的代码库复杂性(例如不必要的依赖关系)。
0
早期的谷歌员工决定使用一个由集中式源代码管理系统管理的共享代码库。这种方法已经服务于谷歌超过16年(本文写于2016年7月,所以大仓模式自2000年起)。
如今,绝大多数谷歌软件资产仍然存储在一个单一的共享存储库中。与此同时,谷歌软件开发人员的数量稳步增加,谷歌代码库的规模呈指数级增长(见图1)。因此,用于托管代码库的技术也在不断发展。
图1. Millions of changes committed to Google’s central repository over time.
本文概述了谷歌代码库的规模,并详细介绍了谷歌定制的单体式源代码库及选择这种模式的原因。谷歌使用自主研发的版本控制系统来托管一个大型代码库,由公司中的大多数软件开发人员使用和可见。这个中央化的系统是谷歌许多开发工作流的基础。这一篇,我们提供了关于系统和工作流的背景信息,使得管理和有效地使用这样一个庞大的代码库成为可能。下一篇我们将解释谷歌的“基于主干的开发”策略以及支持工作流的系统,包括静态分析软件、代码清理和简化的代码审查。
1
Google的单体代码仓库被其 95% 的软件工程师雇员使用,符合超大规模系统的定义,这可以作为单一大仓模式可以成功规模化的证据。
Google 的代码库包括大约10亿个文件,历史记录跨越了Google 的整个18年历程(自98年创建公司开始),约有3500万次提交。该仓库包含 86TB 的数据(注:内容压缩前的总大小,但不包含发布分支上的内容),含有约 900 万个唯一源文件中的约 20 亿行代码。文件总数还包括复制到发布分支的源文件、最新修订版本中已删除的文件、配置文件、文档和支持数据文件。请参阅下面的表格,以获取有关 Google 代码库统计数据的摘要。
Table. Google repository statistics, January 2015
2014年,Google代码库每周约有25万个文件(约1,500万行代码)被变更(注:只包含已审核和提交的代码,不包含由自动化系统执行的提交,以及对发布分支、数据文件、生成的文件、导入到存储库的开源文件和其他非源代码文件的提交)。
大家可以与 Linux 内核的代码量做一个对比。Linux 内核是一个著名的大型开源代码库的例子,它总共包含约1,500万行代码和4万个文件,参见Wikipedia. Linux kernel. Accessed Jan. 20, 2015; 。所以,仅从代码量上来说,Google 相当于每天写一个 Linux 内核。
Google 的代码库由全球数十个办公室的超过 2.5 万名软件开发人员共享。在工作日,他们提交 1.6 万次代码更改,而自动化系统提交另外 2.4 万次更改。每天仓库提供数十亿个文件读取请求,在高峰期交通流量约为每秒大约80万个请求,每个工作日平均约为每秒50万个请求。其中大部分流量请求来自 Google 的分布式构建和测试系统 Blaze,它有一个开源版本,叫作 bazel。
图2 报告了自 2010 年 1 月,至 2015 年 7 月主要代码库每周的独立提交者数量。图 3 报告了在同一时间段内提交到 Google大仓库的提交次数。总提交次数的曲线包括交互式使用情况(或叫人类用户)和自动使用情况的数据。
图2. Human committers per week.
图3. Commits per week.
在两个图表中,较大的下降处是那些影响大量员工的假期期间(例如圣诞节和新年、美国感恩节和美国独立日)。
2012 年 10月,Google 的大仓增加了对 Windows 和 Mac 用户的支持(此前只支持 Linux ),现有的 Windows 和 Mac 代码库已被合并到主代码库中。Google 的代码库合并工具将所有历史更改归属于它们的原始作者,因此在 图2 中出现了相应的上升趋势。这种合并的影响也在 图1 中显现出来。
每周提交次数的图表显示,提交速率一直由人类用户主导。直到2012年,Google 切换到自定义源代码控制实现来托管中央代码库(后面会进一步讨论)。在此转换后,向代码库的自动提交开始增加。提交速率的增长主要是由自动化引起的。
管理这种规模的代码库和在其上的工程活动一直是 Google 面临的挑战。尽管经过数年的实验,Google 仍然无法找到商业或开源版本控制系统来支持这种规模的单个代码库。Google 为存储、版本控制和供应此代码库而构建的专有系统名为 Piper。
2
在讨论使用单一代码库的优缺点之前,需要了解一些关于谷歌工具和工作流程的背景信息。
2.1 Piper 大仓
Piper 存储单个大型代码库,是基于标准谷歌基础架构(最初是 Bigtable ,现在是 Spanner )实现的。Piper 分布在全球 10 个谷歌数据中心,依靠 Paxos 算法确保副本之间的一致性。这种架构提供了高度的冗余性,并帮助优化谷歌软件开发人员的使用延迟。此外,缓存和异步操作可以将大部分网络延迟隐藏在开发人员之后。这很重要,因为要充分利用谷歌的基于云的工具链,需要开发人员在线。
在启动 Piper 之前,谷歌依赖于一个主要的 Perforce 实例,托管在单个机器上,并配合自定义缓存基础架构使用了超过10年的时间。谷歌代码库的持续增长是开发 Piper 的主要动机。
由于谷歌的源代码是公司最重要的资产之一,因此安全功能也是 Piper 设计的关键考虑因素。Piper 支持文件级别的访问控制列表。仓库中的大多数代码对所有 Piper 用户都可见;然而,重要的配置文件或包含业务关键算法的文件可以进行更严格的控制。此外,对 Piper 中的文件的读取和写入访问会被记录。如果敏感数据意外地提交到 Piper 中,则可以清除问题文件。通过读取日志,允许管理员确定在删除文件之前是否有人访问了有问题的文件。
图4. Piper workflow.
在 Piper 工作流程中(参见图 4 ),开发人员在更改文件之前创建存储在工作区中的文件的本地副本。Piper 工作区类似于 Apache Subversion 中的工作副本,Git 中的本地克隆库或 Perforce 中的客户端。可以将 Piper 库中的更新拉入工作区,并根据需要与正在进行的工作合并(参见图 5 )。工作区的快照可以与其他开发人员共享以供审查。在通过谷歌代码审查过程后,工作区中的文件才会提交到中央代码库。
图 5. Piper team logo “Piper is Piper expanded recursively;”
2.2 云端客户端 CitC
大多数开发人员通过称为“云端客户端”( Clients in the Cloud,简称CitC )的系统访问 Piper 。CitC由基于云的存储后端和仅适用于 Linux 的 FUSE13 文件系统组成。开发人员将他们的工作区视为文件系统中的目录,包括他们的变更叠加在完整的Piper代码库上。CitC 支持代码浏览和正常的 Unix 工具,无需在本地克隆或同步状态。开发人员可以在 Piper 代码库中的任何地方浏览和编辑文件,只有修改的文件会存储在他们的工作区。这种结构意味着 CitC 工作区通常只占用很少的存储空间(平均工作区只有不到 10 个文件),同时向开发人员呈现整个 Piper 代码库的无缝视图。
所有对文件的写入都作为快照存储在 CitC 中,可以根据需要恢复以前的工作阶段。可以对快照进行显式地命名、恢复或标记操作,以供代码评审( Code Review,简称 CR)。
CitC 工作区可在任何能连接到基于云的存储系统的计算机上使用,使得无缝切换机器并继续工作成为可能。它还使得开发人员能够在 CitC 工作区中查看彼此的工作。将所有进行中的工作存储在云中是 Google 工作流程的重要组成部分。因此,工作状态可供其他工具使用,包括基于云的构建系统、自动化测试基础设施和代码浏览、编辑和审核工具。
很多工作流程都利用 CitC 中这种未提交代码的可用性,让软件开发人员更加高效。例如,当将更改发送到代码审核时,开发人员可以启用自动提交选项,这在代码作者和审核人员处于不同时区时特别有用。当 CR 被标记为「完成」时,将运行测试;如果测试通过,则将代码提交到代码库,无需进一步人为干预。Google 代码浏览工具 CodeSearch 支持使用 CitC 工作区进行简单的编辑。在浏览代码库时,开发人员可以单击按钮进入编辑模式并进行简单的更改(例如修复拼写错误或改进注释)。然后,他们可以启用自动提交,并在不离开代码浏览器的情况下将变更发送到适当的审核人员。
Piper 也可以在没有 CitC 的情况下使用。开发人员可以将 Piper 工作区存储在他们的本地机器上。Piper 也具有有限的与 Git 的互操作性。今天超过 80% 的 Piper 用户使用CitC,采用率继续增长,因为 CitC 提供了许多优点。
Piper 和 CitC 使在 Google 代码库规模下能够高效地使用单一的、统一的源代码库进行工作成为可能。这些系统的设计和架构都受到了 Google 采用的基于主干的开发模式的重大影响,如下所述。
2.3 主干开发模式
Google 使用 Piper 源代码库,采用基于主干的开发方法。绝大多数 Piper 用户都在最新版本的代码库中工作,即所谓的“主干”或“主线”。所有变更都以单个且串行的顺序应用于代码库。基于主干的开发与中央代码库的组合定义了单一代码库模式。在任何提交后,新代码立即对所有其他开发人员可见和可用。Piper 用户在 Google 代码库的一个一致的视图上工作,这是提供本文后面所述优势的关键。
图 6. Release branching model.
主干开发方法的优点在于避免了长期分支合并时经常发生的痛苦合并。在 Google,分支开发不常见,而且不受良好地支持,而分支通常仅用于发布。发布分支是从特定版本的代码库中分支出来的。那些必须添加到某个发布版的漏洞修复和功能增强通常要先在主线上开发,然后再拣选到发布分支中(参见图6)。由于需要维护稳定性并限制发布分支上的变更,因此发布通常是主干的快照,根据需要从主干中拉出一小部分挑选的提交。在分支和主干上进行并行开发的长期分支使用极为罕见。
Piper 和 CitC 让谷歌高效地使用单一大仓模式成为可能。
在开发新功能时,通常同时存在新旧两种功能代码路径,并通过条件开关进行控制。这种技术避免了需要开发分支的情况,通过配置更新的方式发布,很容易打开和关闭功能,而不是发布一个完整的二进制。虽然这会给开发人员带来一些额外的复杂性,但是可以避免开发分支的合并问题。开关状态的修改可以更容易且更快速地将用户从有问题的新功能实现中切换出来。这种方法通常用于项目特定的代码,而不是通用的库代码。并且,最终标志被废除后,旧代码也可以被删除。Google 在通过不同的代码路径路由实时流量进行实验时也使用了类似的方法,通过配置更改可以实时调整这些 A/B 实验,这些实验可以测量代码的性能特征以及与微小产品变化相关的用户参与度等方面。
2.4 谷歌的工作流程
在基于主干的开发模式中,数千名工程师每天提交数千个变更,需要遵循一些最佳实践和支持系统,以避免主干代码不断出现质量问题。例如,谷歌有一个自动化测试基础设施,几乎每次提交更改时都会启动对所有受影响依赖的重新构建。如果该变更导致大范围的构建错误,就会自动撤销该变更。为了减少提交错误代码的发生率,高度可定制的 Google “预提交”基础设施提供了在将更改添加到代码库之前进行自动化测试和分析的功能。所有变更类似于开发分支的机制,在将新版本暴露给客户端代码之前强制进行额外的测试。
鼓励代码质量是谷歌文化的一个重要方面,所以,在将代码提交到代码库之前会进行代码评审( CR )。除了一小部分高度机密的代码受到更严格的控制之外,大多数开发人员可以查看和提出对跨整个代码库任何文件的更改。通过代码审查过程和代码所有权的概念,减轻了开发人员更改他们不熟悉的代码的风险。谷歌代码库采用树形结构布局,每个目录都有一组所有者( Owner ),他们可以决定是否接受对应目录中文件的变更。Owner 通常是在相关目录中工作的项目的开发人员。变更通常会接受一位开发人员对代码质量的详细审查,包括评估变更的质量,以及接受来自所有者的提交批准,评估变更对他们的代码库区域的适当性。
代码审阅者对代码质量的各个方面发表评论,包括设计、功能、复杂性、测试、命名、注释质量和代码风格,这些都记录在各种特定于语言的 Google 样式指南中。谷歌编写了一款名为 Critique 的代码审查工具,允许审阅者查看代码的演变,并在任何一行代码变更上发表评论。它鼓励进一步修订和交流,最终得到审阅者的最终批准(通常是 LGTM,即 Looks Good To Me" ),表示审阅已完成。
Google的静态分析系统(Tricorder)和 预提交基础设施还可以在 Google 代码审查工具中自动提供有关代码质量、测试覆盖率和测试结果的数据。这些计算密集型检查定期触发,以及当代码更改发送进行审查时触发。Tricorder 还提供一键式代码编辑的建议修复方法来解决许多错误。这些系统提供重要数据,以增加代码审查的效果,并保持 Google代码库的健康。
Google的开发团队偶尔会进行一组大范围的代码清理变更,以进一步维护代码库的健康状况。执行这些更改的开发人员通常将它们分为两个阶段。采用这种方法,首先进行大规模的向后兼容性更改。一旦完成,可以进行第二个较小的更改,以删除不再引用的原始模式。Google 工具 Rosief 支持此类大规模清理和代码更改的第一阶段。使用 Rosie,开发人员通过在整个存储库上执行查找和替换操作或通过更复杂的重构工具创建一个大补丁。然后,Rosie 负责将大型补丁拆分成较小的补丁,独立测试它们,将它们发送到代码审查,并在它们通过测试和代码审查后自动提交它们。Rosie 沿着项目目录线拆分补丁,依赖于前面描述的代码所有权层次结构,将补丁发送到适当的审阅人员那里。
图7. Rosie commits per month.
图 7 展示了每月通过 Rosie 提交的更改数量,展示了 Rosie 作为 Google 进行大规模代码更改的工具的重要性。在使用 Rosie 的同时,需要平衡对团队的成本,因为这些团队需要审核 Rosie 生成的简单更改的持续流。随着 Rosie 的流行和使用增长,明确必须建立某种控制,以限制 Rosie 的使用,使其仅用于高价值的更改,这些更改将分配给许多审核人员,而不是单个原子更改或被拒绝。2013 年,Google 采用了一种正式的大规模更改审查流程,从而导致了 2013 年至 2014 年之间通过 Rosie 提交的提交数量的减少。在评估 Rosie 更改时,审查委员会平衡更改的收益与审核人员时间和代码库翻转的成本。我们稍后将更仔细地讨论这些以及类似的权衡。
总之,Google 已经开发了许多实践和工具来支持其巨大的单体代码库,包括基于主干的开发、分布式源代码库 Piper、工作空间客户端 CitC 和工作流支持工具 Critique、CodeSearch、Tricorder 和 Rosie。
下篇将概述并讨论单一代码库的优点,以及与大规模维护这种模型相关的成本,介绍Piper 的替代品。
乔梁老师开课啦~视频课程《持续部署训练营(Python版)》,原价1699元, 限时特价699元
你的软件开发效率够高吗?质量够好吗?你的团队多长时间才能向用户实时推送一个生产变更?你的软件在发布时,你是否因担心软件交付质量而感到压力倍增?你是否遇到过部署失败,甚至导致停机的情况?持续部署可以帮助你消除软件交付的痛苦,让你能专注于为客户高价值的需求,而不会因为这些交付执行类问题而花太多精力。本课程通过学练结合,理论结合实战,让你体验如何使用构建-测试-部署管道,进行持续部署。并练习如何在有效监控部署的同时,逐步发布功能特性,并作数据库结构变化。
(扫码订阅)