查看原文
其他

【深度全文1.1万字】为什么谷歌将二十亿行代码存储在单一代码库中?

2016-07-04 云头条

早期的谷歌员工决定使用一个共享式代码库,这个代码库通过集中式源代码控制系统加以管理。16多年来,这种做法对谷歌来说屡试不爽;而如今,谷歌的软件资产绝大多数继续存储在一个共享式代码库中。与此同时,谷歌软件开发人员的数量在稳步增加,谷歌代码库的规模也显著增长(见图1)。因而,用于托管代码库的技术也有了长足发展。


历年来向谷歌的中央代码库提交的数百万次变更


主要内容



本文概述了这个代码库的规模,并详细叙述了谷歌专门定制的单一源代码库,以及选择这种模式的若干原因。谷歌使用一套自主开发的版本控制系统来托管一个庞大代码库,这家公司的大多数软件开发人员可以看到并使用该代码库。这个集中式系统是谷歌许多开发工作流程的基础。我们在本文介绍了这些系统和工作流程的背景知识;正是有了这些系统和工作流程,高效地管理和使用这样一个庞大的代码库才变得切实可行。我们解释了谷歌“基于主干的开发”战略和支持系统,它们确定了工作流程的结构,并确保谷歌的代码库状况良好,包括用于静态分析、代码清理和简化代码审查的软件。


谷歌规模


谷歌的单一软件代码库被其在全球各地的95%的软件开发人员使用,它符合超大规模系统的定义,证明了单一源代码库模式可以成功地扩展。


谷歌代码库包括大约10亿个文件,以及大约3500万次提交的代码,跨越谷歌整整18年的发展史。代码库包含86TB的数据,其中包括900万个独特源代码文件中的约20亿行代码。文件总数还包括拷贝到发布分支的源代码文件、最近一次修订时删除的文件、配置文件、说明文档和支持性数据文件;参阅此处的表格,即可了解来自2015年1月的谷歌代码库统计信息摘要。


2015年1月,谷歌代码库方面的统计数字。


2014年,每周谷歌代码库中大约25万个文件中的大约1500万行代码要更改。Linux内核就是个典例,这个庞大的开源软件代码库含有4万个文件中的大约1500万行代码。


谷歌的代码库由来自全球许多国家的几十个办事处的25000多名谷歌软件开发人员共享。在平常的工作日,他们提交16000次代码库更改,自动化系统另外提交24000次更改。每天,代码库要处理数十亿次文件读取请求,高峰期间每秒处理大约80万个查询,每个工作日每秒平均处理约50万个查询。这些流量大部分来自谷歌的分布式构建和测试系统。


每周提交的开发人员数量


每周的提交次数


图2显示了每周向主代码库提交的不重复开发人员数量,时间是2010年1月至2015年7月。图3显示了同一期间,每周向谷歌的主代码库提交的次数。代表提交总数的这条线包括交互式用例(即人类用户)和自动化用例的数据。这两个图中的大幅下降出现在影响众多员工的节假日(比如圣诞节、元旦、感恩节和美国独立日)。


2012年10月,谷歌的中央代码库增添了支持Windows和Mac用户的功能(在此之前仅支持Linux),现有的Windows和Mac代码库与主代码库合并起来。谷歌的代码库合并工具注明了合并的所有历史变更的原始作者是谁,因而图2中出现了相应增加。这种合并的影响在图1中同样很明显。


每周提交次数这张图显示,直到2012年,提交速率一向以人类用户为主。谷歌在2012年改而实施了自定义源代码控制系统,托管其中央代码库,稍后会有讨论。这一转变之后,自动化提交到代码库的现象开始增加。提交速率继续增长,这主要归因于自动化。


对谷歌来说,管理这么大规模的代码库及其上面的活动一向是个挑战。尽管实验了几年,谷歌还是找不到一种商用或开源的版本控制系统来支持如此大规模的单一代码库。为此,谷歌开发了一个代号为Piper的专有系统,负责存储、版本控制和销售这个代码库。


背景知识


在分析处理单一代码库的优缺点之前,需要介绍一下谷歌的工具和工作流程方面的背景知识。


Piper和CitC。Piper存储一个单一的大型代码库,实施在标准的谷歌基础设施上,这套基础设施原先名叫Bigtable,现在名叫Spanner。Piper分布于谷歌在世界各地的10个数据中心,依赖Paxos算法,确保所有副本的一致性。这种架构提供了很高的冗余级别,有助于为谷歌软件开发人员优化延迟,无论他们在哪里工作。此外,高速缓存和异步操作为开发人员隐藏了大量的网络延迟。这点很重要,因为充分获得谷歌那些基于云的工具链的好处需要开发人员在线。


在发布Piper之前的10多年间,谷歌一直依赖托管在单单一台机器上的一个主Perforce实例,结合定制的缓存基础设施。继续扩展谷歌代码库是开发Piper的主要动机。


由于谷歌的源代码是该公司最重要的资产之一,安全功能是设计Piper时考虑的一个主要因素。Piper支持文件级访问控制列表。代码库的大部分是所有Piper用户都可以看见的;然而,重要的配置文件和其他文件(包括业务关键型算法)可以更严格地予以控制。此外,Piper中文件的读取/写入访问被记入日志。如果敏感数据被意外提交到Piper,相应文件就会被清除。读取日志让管理员得以在删除有问题的文件之前确定有人是否访问了该文件。


Piper工作流程


在Piper工作流程中,开发人员在更改之前,先在代码库中创建文件的本地副本。这些文件都存储在该开发人员拥有的工作区。Piper工作区就好比是Apache Subversion中的工作副本、Git中的本地克隆或者是Perforce中的客户端。需要的话,来自Piper代码库的更新可以并入到工作区,与现有工作合并起来(见图5)。工作区的快照可以与其他开发人员共享,以便审查。只有在完成谷歌代码审查过程之后,工作区中的文件才被提交到中央代码库,稍后会有介绍。


Piper的团队标识“Piper就是重复扩展的Piper。”设计者:Kirrily Anderson。


大多数开发人员通过一个名为Clients in the Cloud(即CitC)的系统来访问Piper,CitC包括基于云的存储后端和只支持Linux的FUSE文件系统。开发人员看到的工作区就是文件系统中的目录,包括覆盖在标准Piper代码库上的变更内容。CitC支持代码浏览和平常的Unix工具,不需要本地克隆或同步状态。开发人员可以在Piper代码库上的任何地方,浏览并编辑文件,只有修改过的文件存储在工作区。这种结构意味着CitC工作区通常只耗用少量的存储资源(一个工作区的文件数量平均不到10个),同时向开发人员显示了整个Piper代码库的无缝视图。


文件的所有写入在CitC中作为快照存储起来,因而可以根据需要,恢复之前阶段的工作。快照可以显式命名、恢复或标记,以便审查。


CitC工作区出现在能够连接到基于云的存储系统的任何机器上,因而很容易切换机器,在没有干扰的情况下继续工作。它也让开发人员得以查看对方在CitC工作区中的工作。将所有正在进行的工作存储在云端是谷歌工作流程这个过程的一个重要方面。其他工具因而可以知道工作状态,包括基于云的构建系统、自动化测试基础设施,以及代码浏览、编辑和审查工具。


几个工作流程充分利用了CitC中的未提交代码,让软件开发人员得以更高效地处理庞大代码库。比如说,向外发送变更以便代码审查时,开发人员可以启用自动提交选项;如果代码代码作者和审查人员在不同的时区,这个选项就特别有用。审查被标记为完毕后,测试将运行;如果通过了测试,代码将被提交到代码库,没有进一步的人为干预。谷歌代码浏览工具CodeSearch使用CitC工作区,支持简单编辑。开发人员浏览代码库时,点击一下按钮,就可以进入编辑模式,进行简单的变更(比如修正拼错或改进注释)。然后他们不用离开代码浏览器,在自动提交被启用的情况下,就可以将变更发送给相应的审查人员。


没有CitC,Piper也可以使用。开发人员可以改而将Piper工作区存储在本地计算机上。Piper还与Git有一定的互操作性。如今80%以上的Piper用户使用CitC,由于CitC有诸多优点,采用率继续提高。


有了Piper和CitC,才得以在谷歌代码库这么庞大的规模下,高效地处理单一的整体式源代码库。这些系统的设计和架构都深受谷歌采用的基于主干的开发模式的影响,下面对此作一解释。


基于主干的开发。谷歌在Piper源代码库上采用了基于主干的开发(trunk-based development)。绝大多数Piper用户处理的是单一代码副本(名为“主干”或“主线”)的“头”或最近的版本。以单一、连续的次序对代码库进行变更。基于主干的开发结合中央代码库,造就了单一代码库模式。在任何提交之后,新代码对其他所有开发人员来说都是立即可以看见并使用的。Piper用户拥有谷歌代码库的单一的一致视图,这是提供本文后面介绍的诸多优点的关键。


基于主干的开发大有好处,一方面是由于它在协调长寿命分支(long-lived branch)时,避免了常常出现的令人痛苦的合并。在谷歌,分支开发并不常见,也得不到很好的支持,不过分支通常用于发布。发布分支(release branch)从代码库的特定修订版取下来。必须添加到版本的错误修正版和改进通常按主线开发,随后经过精挑细选,加入到发布分支(见图6)。由于需要维护发布分支的稳定性,减少流失,版本通常是头的快照,根据需要从头获取可选的少量精选内容。长寿命分支以及分支和主线的并行开发结合使用这一现象极为罕见。


发布分支模式


开发新的功能特性后,新旧代码路径通常同时存在,通过使用条件标志(conditional flag)来加以控制。这种方法就不需要开发分支,而且很容易通过配置更新,而不是完整的二进制代码版本,就可以启用和关闭功能特性。虽然给开发人员带来了一些额外的复杂性,但是避免了开发分支的合并问题。有了标志翻转(flag flip),用户撤离有问题的新实施系统容易得多,也要快得多。这种方法通常用于针对特定项目的代码,而不是通用的库代码;最终,标志被停用,那样旧代码可以被删除。谷歌使用了一种类似方法,将实时流量通过不同的代码路径来传送,从而进行试验:通过配置变更,可以实时调整试验。这种A/B实验可以衡量各个方面:从代码的性能特点,到与微妙的产品变更有关的用户参与度。


谷歌的工作流程。需要几个最佳实践和支持性系统,才能避免基于主干的开发模式不断出现的破坏;在这种开发模式下,数千名工程师每天要向代码库提交数千次变更。比如说,谷歌有一套自动化测试基础设施,针对提交到代码库的几乎每一个变更,就会开始重新构建所有受影响的依赖项。如果一个变更造成了大规模的构建破坏,系统就会落实到位,自动撤消变更。为了从源头减少坏代码被提交的几率,可以高度定制的谷歌“预提交”(presubmit)基础设施提供了在变更添加到代码库之前,自动化测试和分析变更的功能。针对所有变更执行一系列全局预提交分析,代码所有者可以创建自定义分析,只对代码库里面他们指定的目录进行分析。一小批非常低级的核心库使用一种类似开发分支的机制,在新版本暴露到客户端代码之前,执行另外的测试。


谷歌文化鼓励代码质量的一个重要方面是,要求所有代码在提交到代码库之前先予以审查。大多数开发人员可以在整个代码库的任何地方,查看并提议文件变更――除了一小部分高度机密的受到更严格控制的代码外。借助代码审查流程和代码所有权这个概念,可以缓解开发人员更改他们不是特别熟悉的代码引起的风险。谷歌代码库采用树状结构来排列。每一个目录都有一组所有人,他们控制着其目录中的文件变更是不是可以接受。所有人通常是从事相应目录中某个项目的开发人员。变更经常受到某一开发人员的详细的代码审查,他评估变更的质量,得到所有人的提交批准后,评估该变更适用于代码库的哪个区域。


代码审查人员评论代码质量的方方面面,包括设计、功能、复杂性、测试、命名、注释质量和代码风格,针对特定语言的诸多谷歌风格指南文档对此有所记载。谷歌编写了一款代码审查工具,名为Critique,它让审查人员可以查看代码的演变以及任何一行变更代码的注释。它鼓励进一步修订和会话,最终审查人员给予“对我来说很好”的评价,这表明审查完毕。


谷歌的静态分析系统(Tricorder)和预提交基础设施在谷歌代码审查工具中,自动提供了代码质量、测试覆盖范围和测试结果等方面的数据。除了代码变更被发送以便审查外,这些计算密集型检查会被定期触发。Tricorder还提供建议的修正版,针对许多错误提供了一键式代码编辑功能。这些系统提供了重要数据,以便改善代码审查的效果,并确保谷歌代码库状况良好。


谷歌的一支开发团队偶尔会进行一系列影响广泛的代码清理变更,进一步维护代码库的健康状况。执行这些变更的开发人员经常把它们分成两个阶段。借助这种方法,先进行一次大的向后兼容的变更。一旦完成后,就进行第二次比较小的变更,去除不再被引用的原始模式。一个名为Rosie的谷歌工具支持这种大规模清理和代码变更的第一个阶段。借助Rosie,开发人员创建一个大的补丁,常常通过针对整个代码库执行查找和替换操作,或者借助更复杂的重构工具。Rosie随后负责把大的补丁分割成比较小的补丁,分别测试它们,然后把它们发送 43 36195 43 15534 0 0 2237 0 0:00:16 0:00:06 0:00:10 3493 43 36195 43 15534 0 0 1914 0 0:00:18 0:00:08 0:00:10 2979出去进行代码审查;一旦它们通过了测试和代码审查,就自动提交。Rosie可以按项目目录来分割补丁,依赖前面所述的代码所有权层次体系,将补丁发送到相应的审查人员。


每月通过Rosie的提交


图7显示了每月通过Rosie提交的变更数量,表明了Rosie这种执行大规模代码变更的工具在谷歌具有的重要性。使用Rosie与团队需要审查Rosie生成的源源不断的简单变更所带来的成本作了权衡。由于Rosie的人气越来越量,使用率越来越高,很显然,需要确立某种控制机制,把Rosie的使用限制于将分发给许多审查人员的高价值变更上,而不是单一的原子变更。2013年,谷歌采用了一种正式的大规模变更审查方法,因而导致2013年到2014年通过Rosie提交的变更数量减少。评估Rosie变更时,审查委员会将这一变更的好处与审查人员时间和代码库流失的成本进行了比较。我们在后面更仔细地分析了诸如此类的优缺点。


总之,谷歌已开发了许多实践和工具,支持其庞大的单一代码库,包括基于主干的开发、分布式源代码库Piper、工作区客户端CitC,以及支持工作流程的工具:Critique、CodeSearch、Tricorder和Rosie。我们在这里讨论了这种模式的优缺点。


分析


这部分概述并详述了单一代码库的优点,以及与维持这种大规模模式有关的成本。


优点。支持超大规模的谷歌代码库,同时为成千上万个用户保持良好性能,这是一大挑战,但谷歌积极采用了单一代码库模式,那是由于它有显著的优点。

最重要的是,它支持:


  • 统一版本控制,单一真相来源;

  • 广泛的代码共享和重用;

  • 简化的依赖项管理;

  • 原子变更;

  • 大规模重构;

  • 跨团队协作;

  • 灵活的团队边界和代码所有权,以及

  • 代码可见性和清晰的树状结构提供了隐式的团队命名空间。


单一代码库提供了统一的版本控制和单一真相来源。哪个代码库托管文件的权威版本方面不存在混淆。如果一个团队想依赖另一个团队的代码,可以直接依赖它。谷歌代码库包括大量有用的库,单一代码库促成了广泛的代码共享和重用。


谷歌构建系统让用户易于跨目录添加代码,从而简化依赖项管理。对项目的依赖项进行更改可触发相关代码的重构。由于所有代码在同一个代码库中实行版本控制,只有一个真相版本,不用担心依赖性的独立版本控制。


菱形依赖项问题


最值得注意的是,该模式让谷歌得以避免“菱形依赖项”(diamond dependency)问题(见图8);如果A依赖B和C,而B和C都依赖D,但是B需要版本D,C需要版本D,就会出现这种问题。在大多数情况下,现在不可能构建A。至于基本库D,很难在不造成破坏的情况下发布新版本,因为所有调用者都必须同时更新。库调用者托管在不同的代码库时,更新起来很难。


在开源世界,依赖项经常被库更新打破,找到一起协同运行的库版本是个挑战。更新依赖项的版本对开发人员来说很痛苦,更新方面的延迟会造成技术债务(technical debt),成本可能会高昂。相比之下,如果是单一源代码树,更新库的那个人同时更新所有受到影响的依赖项就很合理,也来得更容易。一旦进行变更,立即偿还相关系统带来的技术债务。对基本库的更改立即通过依赖项链传播开来,传播到依赖库的最终产品,不需要单独的同步或迁移步骤。


请注意:菱形依赖性问题不仅出现在库与库之间,还出现在源代码/ API层面,如这里所示。在谷歌,二进制问题通过使用静态链接(static linking)来加以避免。


能够进行原子变更也是单一模式一项非常强大的功能。开发人员可以通过一次一致性的操作,进行重大变更,影响跨代码库的成百上千个文件。比如说,开发人员可以在一次提交中更名一个类或一个函数,又不破坏任何构建或测试。

单一代码库拥有所有源代码,或者至少集中式服务器上拥有所有代码,让核心库的维护人员更容易在提交影响重大的变更之前,对它们执行测试和性能基准测试。这种方法适用于探索和测量极具破坏性的变更具有的价值。一个具体的例子是评估让谷歌数据中心改而支持非x86机器架构的可行性的实验。


由于谷歌代码库的单一结构,开发人员根本没必要决定代码库边界在哪里。工程师根本不需要“分叉”共享库的开发,或者跨代码库合并,以更新代码的复制版本。团队之间没有了边界。当项目所有权变更,或者计划合并系统时,所有代码已经在同一个代码库中。有了这种环境,就很容易对代码库进行逐步的重构和重组。可以对代码库进行原子变更,以便移动项目、更新所有依赖项,而受影响代码的开发历史仍保持完好、可用。


单一代码库的另一个属性是,代码库的布局容易理解,因为它用一颗树来加以组织。每个团队在主树里面都有目录结构,实际上充当了项目自己的命名空间。每个源代码文件都有独特的身份,以单一字符串来表示――文件路径可能包括一个版本号。浏览代码库,很容易理解任何一个源代码文件有多适合代码库的大局。


谷歌代码库在不断发展。更复杂的代码库更新改造工作(比如将它升级到C ++ 11或推出性能优化)常常由专门的代码库维护人员集中管理。这样的工作能够影响散布于成千上万个源代码文件的50万个变量声明或函数调用站点。由于所有项目集中存储起来,专家团队就能为整个公司做这项工作,而不是要求许多人来开发各自的工具、技术或专长。


想看看这些好处具体怎样的一个例子,只要考虑谷歌的编译器团队,它确保谷歌的开发人员采用最新的工具链,并得益于所生成代码方面的最新改进和“可调试性”。单一代码库让团队得以全面了解各种语言在谷歌如何使用,让他们得以针对整个代码库进行清理,从而防止变更破坏构建,或给开发人员带来问题。这大大简化了编译器的验证,因而缩短了编译器发布周期,并且让谷歌有可能安全地进行常规的编译器发布(通常每年为C++编译器发布20多个版本)。


利用对整个谷歌代码库的夜间构建版本进行的性能和回归测试生成的数据,编译器团队将默认的编译器设置调成最佳状态。比如说,从2014年到2015年,由于这种集中式工作,谷歌的Java开发人员都看到垃圾回收(GC)的处理器使用量减少了50%以上,GC暂停时间缩短了10%-40%。此外,发现软件错误后,团队常常可以添加新的警告,防止错误再次发生。在进行这种变化的同时,他们扫描整个代码库,查找并修复正在处理的软件问题的其他情况,然后关注新的编译器错误。拥有过去问题重重的编译器拒绝模式,大大提升了谷歌的整个代码运行状况。


将所有源代码存储在一个共同的版本控制库中,让代码库维护人员得以高效地分析和变更谷歌的源代码。像Refaster和ClangMR(常常与Rosie一起使用)这些工具充分利用了谷歌源代码的单一视图,对源代码执行总体转换。单一代码库捕获所有的依赖项信息。旧的API可以放心地删除,因为可以证明:所有调用者已迁移到新的API。单一的通用代码库大大简化了这些工具,可以确保变更原子性以及整个代码库在任何时间都有单一的全局视图。


成本和取舍。需要注意的是,单一代码库绝不意味着单一软件设计,使用这种模式既面临一些取舍,又存在一些缺点,必须考虑清楚。


这些成本和取舍分为三类:


  • 购置开发和执行所需的工具;

  • 代码库的复杂性,包括不必要的依赖项和发现代码方面的困难,以及

  • 为确保代码状况良好所花的精力。


从许多方面来看,单一代码库带来了较简单的工具,因为对处理源代码的工具来说,只有一个参考系统。然而,工具也有必要扩展,以适应代码库的规模。比如说,谷歌为Eclipse集成开发环境(IDE)编写了一个自定义插件,以便可以从IDE来处理庞大的代码库。谷歌的代码索引系统为Emacs、Vim及其他开发环境支持静态分析、代码浏览工具中的交叉引用,以及丰富的IDE工具。这些工具需要长期的投入,以便管理规模越来越庞大的谷歌代码库。


除了致力于构建和维护可扩展的工具外,谷歌还要承担运行这些系统的成本,其中一些系统是非常计算密集型的。谷歌的内部开发工具套件有许多对于支持单一代码库的规模来说至关重要,包括自动化测试基础设施和高度扩展的构建基础设施。因此,有必要做出取舍,确定多久运行这种工具,以便权衡执行成本与数据给开发人员带来的好处。


有了单一模式,更容易理解代码库的结构,因为依赖项之间不存在代码库边界的交叉。然而,随着规模增大,发现代码会变得更困难,因为像grep这些标准工具陷入困境。开发人员必须能够探索代码库,找到相关的库,看看如何使用它们、谁编写了它们。库作者常常需要查看其API在如何使用。这需要大力投入于代码搜索和浏览工具。然而,谷歌发觉这种投入回报丰厚,可以提高所有开发人员的生产力,Sadowski以及其他人对此有更详细的描述。


访问整个代码库有助于广泛的代码共享和重用。有人会说,这种模式依赖谷歌构建系统的高度可扩展性,太容易添加依赖项,减小了软件开发人员开发稳定的、深思熟虑的API的动机。


由于易于构建依赖项,团队常常不考虑依赖项关系图,因而让代码清理更容易出错。不必要的依赖项会加大项目面临下游构建破坏的风险,导致二进制代码太过庞大,并带来构建和测试方面的额外工作。此外,如果仍留在代码库中的废弃项目继续得到更新和维护,浪费生产力的现象会随之而来。


谷歌的几个项目力求控制不必要的依赖项。有些工具有助于识别并删除未使用的依赖项,或者因历史原因或意外原因而链接到产品二进制代码的依赖项,它们并不需要。还有一些工具可识别未充分使用的依赖项,或者是依赖基本上不需要的庞大库的依赖项,它们是重构的合适对象。Clipper就是这样一种工具,它依赖自定义的Java编译器,生成准确的交叉参考索引。然后,它使用索引来构建一个可达图(reachability graph),并确定哪些类从来不用。Clipper适用于引导依赖项重构工作,因为它可以找到比较容易删除或拆解的目标。


依赖项重构和清理工具大有帮助,但是理想情况下,代码所有人应该能够从源头来防止不需要的依赖项被构建。2011年,谷歌开始依赖API可见性(API visibility)这个概念,将新API的默认可见性设为“私密”。这迫使开发人员将API明确标记为适用于其他团队使用。从谷歌尝试大型单一代码库可以汲取的一个经验就是,这种机制应尽快落实到位,鼓励状况更良好的依赖项结构。


谷歌大部分代码可供谷歌的所有开发人员使用,这带来了这种文化:一些团队要求其他开发人员读取代码,而不是为他们提供不同的用户文档。这种方法有优点和缺点。不需要花力气编写文档,或确保文档版本最新,但开发人员有时不仅仅阅读API代码,最后要依赖底层的实现细节。这个行为会给团队带来了维护方面的负担,团队之后很难废弃从未想过暴露给用户的功能。


这种模式还需要团队在使用开源代码时彼此合作。代码库的一个区域专门用于存储开源代码(谷歌开发或外部开发的代码)。为了防止如上所述的依赖项冲突,在任何一个时间,开源项目只有一个版本很重要。使用开源软件的团队需要偶尔花时间升级代码库,以便在执行库升级时,可以使用开源库的更新颖版本。


谷歌投入大量的精力来维护代码健康状况,处理与代码库复杂性和依赖项管理有关的一些问题。比如说,特殊工具可自动检测并清除无用代码,分割庞大重构组件,并自动分配代码审查(通过Rosie),并将API标记为已废弃。需要开发人员运行这些工具,并管理相应的大规模代码变更。团队还带来了成本,因为需要审查来自整个代码库清理和集中更新改造工作带来的源源不断的简单重构。


替代方案


由于像Git这些分布式版本控制系统(DVCS)的人气越来越旺、使用越来越广,谷歌曾考虑要不要从Piper迁移到Git,作为主要的版本控制系统。谷歌的一个团队专注于支持Git,谷歌的Android和Chrome团队在主要的谷歌代码库之外使用Git。由于外部合作伙伴和开源协作,使用Git对这些团队来说很重要。


Git社区强烈建议并偏爱开发人员拥有更多更小的代码库。Git克隆操作需要把所有内容拷贝到某人的本地系统,这个过程与庞大的代码库不兼容。想改成基于Git的源代码托管,有必要将谷歌的代码库分割成数千个独立的代码库,才能获得合理的性能。这种重组势必需要谷歌的开发人员改变文化和工作流程。相比之下,谷歌的托管在Git上的Android代码库分成了800多个独立的代码库。


考虑到从谷歌构建的现有工具获得的价值,以及单一代码库结构具有的许多优点,很显然,改用更多更小的代码库对谷歌的主代码库来说不合理。改用Git 或需要分割代码库的其他任何DVCS这种替代方案对谷歌来说都缺乏吸引力。


谷歌源代码团队目前的投入主要致力于确保内部源代码系统的日常可靠性、可扩展性和安全性。该团队还在积极尝试Mercurial,这是一种类似Git的开源DVCS。其目的在于,为Mercurial客户增添可扩展性功能,那样它就能高效地支持规模如同谷歌的代码库。这将为谷歌的开发人员提供一种替代方案:使用DVCS式样的流行工作流程,并结合中央代码库。谷歌正与开源Mercurial社区开展这方面的合作,包括来自注重单一源代码模式的其他公司的代码贡献者。


结束语


1999年,谷歌选择了单一源代码管理策略,当时现有的谷歌代码库从CVC迁移到Perforce。早期的谷歌工程师坚持认为,单一代码库比分割代码库来得合理,不过当时他们并没有料到代码库的未来规模以及为了让扩展切实可行而构建的所有支持性工具。


这些年来,由于继续扩展集中式代码库需要的投入越来越大,谷歌领导层偶尔考虑是不是有必要迁离单一模式。尽管要付出大量努力,但由于具有诸多优点,谷歌还是一再决定坚持使用中央代码库。


源代码管理的单一模式并非适合每个人。它最适合像谷歌这样的企业组织,拥有开放、合作的组织文化。它并不适合这样的企业组织:代码库的大部分是私有的,或者隐藏在小组之间。


我们在谷歌还发现,如果给予一些投入,源代码管理的单一模式可以成功扩展,适应这样的庞大代码库:拥有10亿多个文件、3500万次提交,以及全球成千上万个用户。随着谷歌内外的项目其规模和复杂性不断扩大,我们希望,本文描述的分析和工作流程有助于其他企业在代码库的长期结构方面做出明智的决定。


致谢


我们想感谢谷歌开发者基础设施(Google Developer Infrastructure)团队的所有新老成员,感谢他们致力于构建和维护文中提到的系统,同时感谢帮助审核本文的许多人,特别感谢:乔恩·珀金斯(Jon Perkins)和英戈·沃尔瑟(Ingo Walther),他们是Piper的现任技术负责人;凯尔·利平科特(Kyle Lippincott)和克拉彻·邓纳凡特(Crutcher Dunnavant),他们是CitC的前任和现任技术负责人;希鲁姆·赖特(Hyrum Wright),谷歌的大规模重构专家;克里斯·科洛汉(Chris Colohan)、凯特琳·萨多斯基(Caitlin Sadowski)、摩根·埃姆斯(Morgan Ames)和罗布·西门博斯基(Rob Siemborski),以及Piper和CitC开发及支持团队,感谢他们做出了颇有见地的审查评论。


云头条编译|未经授权谢绝转载


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存