作者 | Rachel Potvin,Josh Levenberg
译者 | 张建军
编辑 | apddd
【AI科技大本营导读】与大多数开发者的想象不同,Google只有一个代码仓库——全公司使用不同语言编写的超过10亿文件,近百TB源代码都存放在自行开发的版本管理系统Piper中,只当项目开源且需要外部协作时,才会使用业界流行的Git。本文虽然发表于2016年,但是详细解读了Google采用这一方案背后的原因与经验,直到今天仍然有很大的借鉴意义,值得一读。
早期 Google 员工决定使用集中式源代码管理系统。这种方法已维持了19年以上,至今,绝大多数软件仍然存储在这个共享代码库中。与此同时,随着 Google 软件工程师数量稳步增加,代码库的规模呈指数增长(见图1)。 因此,用于托管代码库的技术也发生了显著变化。
关键点:
Google 代码库包含大约十亿个文件,约3500万次提交记录。该代码库包含 86TB 的数据,包括分布在900 万个源文件的约 20 亿行代码。 文件总数还包括复制到发布分支的源文件、最新版本删除的文件、配置文件、文档和支持性数据文件。
2014 年,每周在 Google 代码库中约有1500 万行代码被修改,涉及文件数约25万个。Linux 内核是一个典型的大型开源软件代码库,该代码库包含 40,000 个文件,约 1500 万行代码。
Google 的代码库由来自世界各国数十个办事处的 25000 多名 Google 软件开发人员共享。 在工作日,他们通常会对代码库提交 16000 次更改,另有 24000 次更改由自动化系统提交。每天,代码库处理数十亿次文件读取请求,峰值每秒大约有 80 万次查询,工作日平均每秒大约有 50 万次查询。大部分流量来自 Google 内部的分布式编译系统bazel。
在审视使用单一代码库的优缺点之前,需要了解一些 Google 使用的工具和工作流程。
Piper和CitC
Piper是一个大型单代码库,最初是基于 BigTable,现在是基于Spanner实现。Piper 分布在全球 10 个 Google 数据中心,依靠 Paxos算法来保证副本一致性。该架构提供了高冗余,并对延迟进行了优化。此外,缓存和异步操作可以隐藏大量网络延迟。
在推出Piper之前,Google 使用的是运行在一台机器上的Perforce(加上自定义缓存基础架构,提供服务超过10年)。Google 代码库规模不断变大是开发Piper的主要原因。
由于 Google 的源代码是公司最重要的资产之一,因此安全功能是 Piper 设计的关键考虑因素:
在 Piper 工作流程中,开发人员在更改代码库之前会创建文件的本地副本。 这些文件存储在开发人员的工作区中。Piper 代码库中的更新可以根据需要被pull到工作区并与正在进行的工作进行合并。可以与其他开发人员共享工作区快照以供审查。工作区中的文件仅在经过 Google 的代码审查过程后才会被提交到主代码库。
大多数开发人员通过名为Clients in Cloud 的系统或 CitC 访问Piper,该系统由基于云的存储后端和 Linux FUSE文件系统组成。开发人员的工作区是文件系统中的一个目录。
CitC支持:
Piper 也可以在没有 CitC 的情况下使用。开发人员可以将 Piper工作区存储在本地计算机上。 Piper 还可以和 Git 进行有限的互操作。目前,超过 80% 的 Piper 用户使用CitC,由于 CitC 有许多优势,使用率还在持续增长。
基于主干的开发
Google 在 Piper 源代码库之上实施基于主干的开发。绝大多数Piper用户在“头部”(head)进行开发,指“主干”(trunk)或者“主线”(mainline)代码最新版本的一份副本。对代码库的更改是单一串行的。在任何代码提交之后,其他所有开发人员都能看到并使用新代码。
在Google,通常只在发布上线时才会使用分支。发布分支是从代码库某次修改中分割出来的。漏洞修复和必须添加到发布版本的增强功能通常是在主干上进行开发,然后进行cherry-pick引入到发布分支(参见图3 )。由于需要保持稳定性并限制发布分支上的过多变动,所以发布版本通常是“头部”的快照,根据需要可以从“头部”进行cherry-pick更新代码。
当开发新功能时,新旧代码逻辑通常同时存在,通过使用条件标志来控制。这种技术避免了开发分支的需要,并且通过配置更新可以轻松启用或者关闭某项功能。虽然给开发人员增加了一些复杂性,但是避免了开发分支合并问题。标志翻转使得切换具有问题的新实现变得更加容易和快捷。该方法通常用于项目特定的代码,而不是通用的库代码,且最终会删除标志和旧代码。
Google工作流程
Google采用了几种最佳实践和支持系统,以避免在基于主干的开发模式中碰到的问题。
自动测试基础设施:Google内部的自动测试设施可以对几乎所有由于代码更改而受影响的依赖项重新编译。如果一次代码更改造成编译失败,系统就会自动回滚撤消更改。为了减少错误代码被提交到主代码库的可能性,Google采用了一个内部使用的“预提交”系统,可以在更改代码添加到代码库之前自动进行测试和分析。可以针对所有更改运行一组全局预提交分析,代码所有者也可以创建仅在其指定代码库中的目录上运行的自定义分析。
代码审查:代码审查者会对代码质量进行评价,包括设计,功能,复杂度,测试,命名,注释质量和代码风格(Google为不同语言编写了不同的风格指引文档)。Google编写了一个名为 Critique 的代码审查工具,允许审查者查看代码的演变,并对任何一行更改进行评价或吐槽。Google鼓励开发人员不断修改并与审查者进行交流,当审查者最终意见为“LGTM”(Looks Good To Me,我觉得可以)时,审查过程才算完成。
Google 代码库以树结构呈现:每个目录都有一组所有者控制,由他们决定是否接受对目录中文件的更改。变更通常会经过一位开发人员进行详细的代码审查,以衡量变更的质量,以及所有者的认可批准,以评估该变更是否适合他们所在的代码库位置。
静态分析系统(Tricorder )和预提交系统:这些系统在 Google 代码审查工具中自动提供有关代码质量,测试覆盖率和测试结果的数据。 这些密集检查会周期性地,或者当有代码修改需要审查时被触发。Tricorder 还为许多错误提供了一键修改建议。
代码清理:Google使用Rosie进行大规模清理和代码更改。开发人员可以创建一个大补丁,然后Rosie负责将大补丁分成较小的补丁进行独立测试,并进行代码审查,并在通过测试和代码审查后自动提交。Rosie 根据项目目录拆分补丁,依靠代码所有权层次结构将补丁发送给合适的审查者。
图4现实了每月通过 Rosie 进行的更改提交次数,表明 Rosie 作为 Google 大规模代码更改的工具的重要性。使用Rosie需要注意其使用成本。2013 年,Google 实施了正式的大规模代码更改-审查流程,导致了从 2013 年到 2014 年通过 Rosie提交更改的数量减少。在评估 Rosie提交的更改时,审查委员会需要在更改带来的收益和审查者时间、代码库大幅变动带来的成本之间权衡。
总而言之,Google 使用了许多策略和工具来支持其庞大的代码库,包括基于主干的开发,分布式源代码存储库 Piper,工作区客户端 CitC 以及工作流支持工具 Critique、CodeSearch、Tricorder和Rosie。下面我们讨论这个模型的利弊。
优点(单代码库模型支持)
成本和权衡
开发和执行所需的工具投资
代码库复杂性,包括不必要的依赖性和代码查找的困难
为代码健壮性付出的心血
随着像Git这样的分布式版本控制系统(DVCS)的普及和使用越来越多,Google 曾考虑过是否将Piper转移到Git作为其主要的版本控制系统。 Google 有一个团队的任务是支持Git供 Google 的 Android 和 Chrome 团队在主代码库外使用。由于外部合作伙伴和开源协作,使用 Git 对于这些团队很重要。
要转移到基于 Git 的源代码托管,需要将 Google 的主代码库拆分成数千个独立的代码库才能实现相当的性能。这样的重组需要改变Google开发人员的文化和工作流程。作为比较,Google 用Git 托管的Android代码库被拆分为 800多个不同的代码库。
Google 源代码团队目前的投入主要集中在内部源代码系统的持续可靠性,可扩展性和安全性上。该团队目前正在试用Mercurial,这是一款类似Git的开源DVCS。目标是向Mercurial客户端添加可扩展性,以便高效地支持Google规模的代码库。这样,Google工程师可以通过流行的DVCS风格的工作流来使用单代码库。
源代码管理的单一模型不适合所有人。它最适合像 Google 这样的组织——具有开放和协作的文化。而对于代码库中大部分是私有的,或者组与组之间代码不可见的组织来说并不适用。
原文链接