​从微信后端仓库发展史谈谈单仓和多仓

386次阅读  |  发布于3年以前

最近某些代码大仓的思想风靡整个 DevOps 圈子,这里的所谓大仓指的并不是仓库的代码容量达到多少 G,而是一种指导思想——就一个公司的代码(如 Google)的增长趋势,代码的趋近于整合到一个单一的仓库的,而非趋于分割为多个仓库分开存储的。但除了 Google、Microsoft 这些历史悠久的公司实践了大仓,其它的新兴公司几乎都没有一个划分仓库的规则。 本文从微信后端的代码仓储方式的历史说起,一步步的描述微信后端是如何构建一套真实可用的小仓方案的,并将这样一种分仓的方法分享出来,希望实践微服务的同事们有所帮助。

其实或许不应该使用小仓这个词来描述当前微信后端的代码组织方式,就占用空间而言,微信后端的某些仓库从体积上来说并不算得上是小 ,微信支付后端的旧代码仓库项目截止 2021-08-23 已使用了超过 2T 的存储空间。这里使用小仓这个词汇实际上对应英语中的 multirepo 或 polyrepo,也就是相对于单仓 monorepo 概念中的多仓。

本文中所描述的仓库的大小划分并不是按照其占用空间划分,而是提供一种划分代码仓库的思想:

本文将从微信后端的代码仓库的发展历史开始,逐个分析微信后端代码的演化过程,试图从大仓逐渐分解成小仓的历史进程来解构小仓研发流程的客观规律,并最终提出小仓的整体解决方案。欢迎提出任何理性的讨论和建议。

1 微信后端代码仓库发展史

以人为鉴可以知得失,以史为鉴可以知兴替。

了解微信后端的开发都知道,微信这个项目启动的非常偶然,微信最初来源于广州研发部的一个创意,微信后端天生就继承了 QQ 邮箱非常多的代码。你仔细研究代码就会发现,在很多部署的路径中都是用了 QQMail 这样的 hardcode(当然现在并不推荐这样做)。这里就来先回顾一下后端代码仓库的发展历史。

1.1 单一svn仓时代 (2011~2013)

最开始的年代微信后台是直接复制了一份邮箱的代码然后在上面修改的,当时的微信后端代码是一个单仓其中的代码示意结构如下。

大部分的代码都是原封不动的从 QQ 邮箱复制过来,只是创建一个新的 svn 仓库用于编写业务代码,可是由于业务的快速发展,代码的管理也处出现了一些不和谐的音符。

由于没有统一构建系统,变更一个微服务的基本流程是野蛮的。

  1. 在本机调试好代码后上传到 svn;
  2. 在一台专用的编译机上更新 svn;
  3. 使用魔改版的 blade 编译 release 版本的二进制;
  4. 如果要发配置、前端页面模板,需要手动将文本文件 scp 到编译机上(当然你也可以把开发版本的二进制直接 scp 到编译机上,省掉1,2,3);
  5. 去微信运维门户提交发布单,告诉发布系统需要更新那些文件,需要重启那些服务;
  6. 微信运维门户发布到预发布环境;
  7. 灰度→全量上线。

单元测试全凭自觉,上线前也不会跑 cc_test,所以不写单元测试也没关系,就是这么野蛮。

1.2 拆分svn时代 (2013~2018)

随着业务的成长,在单一 svn 仓库逐渐不满足的业务的开发了,2013 年左右逐渐开始将非微信基础核心的业务部门的代码,从 MicroMsg_proj 仓拆出去。各个业务团队如微信支付、开放平台基础部、开放平台业务部、微信游戏等都有了自己的仓库。由于 svn 可以检出子目录,且可以通过目录来设置权限,代码库的目录划分开始和组织架构挂钩。

当时编译机的目录结构不再是映射单一的仓库,而是第一级映射的是某个仓库的 trunk 目录下的子目录,例如开放平台创新部的工作区目录是:

所有的一级子目录都对应 svn 的一个子目录,不同子目录的权限由各个业务方维护,比如微信基础(广州)负责维护基础框架和微信底层通信代码,开放平台基础部(广州)负责维护维护开放平台的业务代码,开放平台业务部(深圳)负责维护摇一摇电视 ibeacon wifi 游戏等创新项目,微信支付(广深)负责维护支付底层业务代码。

此时实际上微信后端已经从 monorepo(单仓)走向了 multirepo(多仓)模式。

同时为了解决越来越多的依赖混乱的问题,广研在后端微服务工具链中增加了对依赖的强制校验。并规定:

  1. 基础组件中的定义的 Protobuf 源文件可以被所有的目录依赖;
  2. 部门的对外服务,其中的 Protobuf 定义的结构可以被其他所有非基础组件依赖。例如,创新业务内部服务可以依赖微信支付对外服务、开放平台后端对外服务下的定义的结构;
  3. 非对外服务的只能依赖本部门对应的私有仓的微服务和其他对外的服务定义的结构,比如创新业务对外服务可以依赖创新业务内部服务、开放平台对外服务但不能依赖开放平台内部服务和微信内部服务。

每一个项目组都有独立的编译机,在这个编译机上会有多个编译账户用来管理编译工作区(这个编译账户在后面的统一编译也会用到),有了不同的编译账户就从编译机上解决了某些代码禁止依赖的问题。

  1. 微信支付编译账户不会拉取微信后端内部服务、开放平台后端内部服务、创新部内部服务的代码,所以在微信支付的编译账户下,编译脚本如果分析出有微信后端内部服务、开放平台后端内部服务、创新部内部服务下的依赖,就会提示路径不存在(注意此时 bazel 还没有开源,也没有在包管理的概念);
  2. 通用框架使用的账户不能拉取任何业务代码的仓库,所以基础库的如果依赖了某个业务代码将不会编译通过;
  3. 以上规则只在编译机上生效,如果你是在开发机,还是可以编译通过的,这也就是为什么现在依然存在某些单元测试编译目标依赖混乱的问题,因为这些单元测试并不需要在编译机上编译。

正是有了不同的编译账户和各个部门不同的编译机,使得不同业务部门之间有了高度的独立性。高度受政策管控的微信支付业务可以使用稳定版本的基础库,而激进的开放平台创新部以及后来的游戏中心却非常乐于体验带新特性但不那么稳定的基础库框架的版本,而这样的选择是由于业务的迭代特性的自发的选择,并非由某位大佬自上而下的强制推行。

1.3 迁移git时代(2018~2020)

2018 年到 2019 年公司启动仓库由 svn 到 git 的过程。公司的出发点非常好,不强制业务选择迁移的方案,广研团队和微信支付团队分别提出了两种不同的方案。广研的方案比较粗暴直接:

此时的后端编译仓库不再是扁平的二级结构,而是树形结构,每个节点都和一个 GIT 仓库对应。这样做的好处非常明显:

  1. 后端的代码工作区的结构和组织架构解耦,根据功能来划分。以前需要增加一个第三方库,需要到处求爷爷告奶奶,求广研基础部在自己管理仓库 mm3rd 下增加,到时候出了事,责任难界定;
  2. 统一管理第三方库。第三方库统一收归到 mm3rd2 下,完全是一个开发协作的思想,你增加了第三方库挂载到伏羲平台中,自己的编译账户就可以使用了,再也不需要自己硬复制文件到自己的目录下了;
  3. 编译依赖和库版本解耦。曾经使用 bazel 写依赖项需要带上版本号,svn 时代都是大一统的单仓,根本没办法对依赖项统一规划。某些时候不得不写成 //mm3rd/boost-1.66:regex 这样奇葩的写法,如果其包含的次级依赖中又依赖了 //mm3rd/boost-target:regex ,就会存在符号冲突,现在统一写成 //mm3rd/boost-target:regex 即可,而使用不同的编译账户,就可以完美的切换不同版本的第三方库;
  4. 公共组件可以及时共享协作了。新增的 mmcontrib 非常开放的将所有想为 WXG 后端建设的成员开放,并集成到统一编译中,并使用了专用的编译账户以保证不能依赖业务仓库中的代码。
  5. 代码仓库项目组和前后端构建系统解耦。越来越多的项目采用工蜂项目组的形式来管理,比如一个项目中同时存在前端、客户端、小程序、后端不同的仓库,以前这些代码都需要被粗暴的拉到本地,但对于微信后端代码的构建系统来说,客户端、小程序、前端等并不需要被看到,现在只有那些需要被微信后端构建系统构建的代码才会被检出。

迁移到 git 后,广研基础团队提出了改进 GitFlow 的工作流模式。

然而技术架构部所管理的 comm2 基础库使用了主干开发模式。

技术架构部提供了 PreMaster 分支给业务方使用。PreMaster 分支的特性要优先于 master,有些业务方和基础架构部共建的特性会优先让业务方使用,稳定运行过一段时间后,PreMaster 会合并到 master。之后 PreMaster 的特性就会应用到所有 WXG 后端服务代码中。

1.4 根据领域驱动拆分小仓(微信支付整洁 git) (2020~现在)

在迁移到 git 时引出了另外的问题,如何划分工蜂项目组?工蜂项目需要由什么样的规则来管理呢?

微信基础部也曾经遇到这样的问题,当时只是仅仅通过行政组织架构来对应文件夹,最终导致了依赖混乱,代码耦合严重等问。

基于这样的问题,支付团队决定进一步将代码仓库拆小,在迁移 GIT 的过程时,支付提出了整洁 GIT的方案。

整洁 GIT 定义了一系列的使用规范。

这样有计划的一致的拆分方案解决了使用大仓的一系列问题。

1 . 权限独立

a . svn 中通过组织架构和文件夹权限对应,一旦组织架构调整,文件夹而又不可能随着组织架构一起调整,最终导致编写代码混乱;

b . 整洁 GIT 将代码通过业务系统拆分,同时工蜂可以将组织架构动态绑定到项目组或项目中,当组织结构有变动时,可以非常的方便的调整权限。

2 . 持续集成

a . 整洁 GIT 使用 CI 工具收敛了变更最多、协同最多的服务契约,者将会使得因为服务契约协同开发而导致的拉分支状况相当少,只要在契约系统中完成契约编辑,会自动将契约转化成 client.h``client.cpp 并放置到对应的专用仓库中;

b . 不同项目组由于业务性质不同,可以采用不同的 CI 策略。如基础支付的产品生死攸关,必须添加单元测试流水线,代码覆盖率超过 80%,才能被合入主干发布;而行业运营开发组项目大多数实验性项目,产品的生命周期很短,并不要求完美的单元测试,只需要保证代码基本的风格统一即可。

3 . 工作流优化

a . 由于将代码仓库拆小,所有的发布关联和问题集成都可以在工蜂中完成,特别是做基础组件的团队,已经非常熟悉 GitHub 一套 MR 流程,工蜂的使用效能达到最大化。而大仓除了提供一堆 99+ 的数字、无数五颜六色的 label 毫无利用价值,最终弱化成带历史记录的超大文件系统

b . 各个编译系统可以根据不同的编译产物进行构建,自由组合虚拟的编译工作区,而不是像无头苍蝇一样把所有的代码都拉取下来。例如:编译后端代码,只需要拉取编译账户下的后端代码仓库,忽略掉项目组中其他的例如前端、小程序、私有组件仓库。同时,若使用前端构建方案,则只需要检出一个单独的前端仓库即可;

c . 由于依赖的清晰化,从构建工具开始就对一些无效的依赖限制,如根据领域驱动设计来确定某些依赖项需要被斩断。

代码仓库一直都是伴随着开发一生绕不开的话题,随着业务的膨胀,代码仓库也会一天天变的巨大,无论大仓还是小仓。都必须是在业务的发展中经得起检验的,而回顾这微信发展的 10 年,团队也一直通过实践的方式优化代码仓库的合理使用,并在使用过程中结合了软件方法、领域驱动建模等非仓库的理论来实践,一步一步的发现问题解决问题。而并不是一开始就弄一个超大的概念,制定超过模糊的强规则让众多开发屈服,只有同时让开发用的爽(提升研发效能)让老板满意(满足组织上一致性)的方案才是最优秀可行的解决方案。

2 小仓的优秀实践(效能篇)

某研发:你的方案怎么好,你来写啊,别光说不做

首先需要承认一点的是,对于效能工具团队,广大的一线程序员就是最好的调研目标,让他们开发起来爽,他们就更加有干劲的产出,同时要保证服务质量,就是我们存在的价值。如果弄一堆空中楼阁式的自研工具,搞一套业界差别很大的理论,让开发人员疲于奔波和学习某深奥些理论知识,招来的不仅是一线业务团队的拒绝合作,更会严重拖慢业务精益迭代的目标。

2.1 全局代码搜索

由于使用了小仓和虚拟工作区的方法来处理各个开发机后端代码,在每位开发本机开发或远程编译时,并不需要耗费非常长的时间把所有的代码都下载到本机,只需要检出自己有权限的仓库(如果你使用远程编译,甚至是你当前开发项目的仓库)。

此时如果需要查询某个符号的引用,如果在本机搜索当前工作区的副本显然是不现实的(特别是后端代码),如果直接使用全文搜索,不仅效率低,而且还费时,搜索的结果也差强人意(不能使用语义的搜索,按照符号类型搜索等),这里时候就有必要构建一个在线的准实时的全局代码搜索门户。

无论是使用 OpenGrok 还是使用 [Mozilla DXR](Overview — DXR 2.0 documentation) 都是非常成熟的解决方案,但这样的解决方案都是基于单机目录设计的,如果一个超级大仓都不能在一台机器上被检出,那几乎无法简单的做出一个非常好用的代码搜索门户。

微信后端团队在 2013年左右就部署了一套基于 OpenGrok 的代码搜索工具,当时的微信后端的仓库比较小,一套门户就适用所有的微信后端的开发。但随着编译账户的增多,代码库的数量也在增加慢慢一台单机已完全不满足所有微信后端代码仓库的搜索了。

基于这样的特点,微信技术架构部开始使用不同账户分治的构建代码搜索门户,并使用了更强大的 [Mozilla DXR](Overview — DXR 2.0 documentation) 方案。这样使得微信基础团队不用再去关心支付的业务代码,微信基础消息收发的代码也不必在支付团队的成员中显示。

大仓的支持者会想当然的会认为做搜索是不是直接弄一个超大仓做一个类似 Google 的搜索页是不是更好呢,这个问题我们就需要回到最初的一线开发者愿景(Want)上来分析。

那一线开发到底是怎么来看待这个问题的呢?

所以全局代码搜索这个需求服务的目标人群是:具有超大规模的后端编译需求的开发们

这样看起来将后端编译的工作区,作为搜索的数据源来分析是最合适不过了,如果整个 WXG 是一个大仓,或者全公司是一个大仓那么想这样简单做一个搜索项目出来就非常困难。

2.2 第三方开源库治理

小仓的方案被很多人诟病的原因是依赖混乱,很多开发使用第三方库直接就在自己的仓库的文件夹下复制一份代码,最终造成整个编译工作区很多重复的开源库。然后就有人提出使用大仓来统一管理开源的三方库。

在单一 svn 仓时代微信后端编译工作区也确实会遇到这样的问题,因为在大仓时代文件夹的权限和组织结构强绑定,掌握 mm3rd 的开发者不愿意为其他开发者将第三方库引入到编译工作区中,因为 mm3rd 的开发者通常是最先创建仓库的人,而非专职处理人员。通常情况下这些开发者会认为引入第三方库会引入风险,故会对需求方的建议推诿、拖延、甚至拒绝执行,而需求方迫于业务时间上的压力,不得不将库粗暴的直接拷贝到自己有权限的文件夹下,最终造成第三方库引用混乱的问题。事实上正是因为大仓耦合了太多功能,才导致第三方开源库治理的混乱,大仓耦合了组织架构权限、构建系统、代码检查规范等,导致某些时候不得不为了非存储代码的目标,而一定要将第三方开源库引入到库中

在以静态语言为基础的构建系统中,这个问题会出现的尤为突出,而且会引起链接问题从而阻塞业务上线流程。例如,下图中的 svrkit 框架升级到 v1.1 后,业务代码将无法链接通过,因为 libboost-1.66 和 libboost-1.55 的符号有冲突。

大仓的拥护者们提供了一个非常理想的方案,将所有的依赖都扁平化放置到一个专用的文件夹下,并收归这个文件夹的权限给专有人员统一管理。貌似一定程度上解决了开源库治理的问题。

此时在升级过程中只需要将业务代码中不兼容的升级修改掉就解决了,大仓的拥护者们兴奋的表示自己解决了世纪性依赖地域的难题。但从仓库和构建系统角度来看,这些开源库的代码根本就不应该放置在大仓的某个文件夹中,而应该是只和编译系统相关,无论是 git 的 sub_module 还是 npm 的 package.json 都是通过声明的方式将这些第三方库临时的添加到构建个工作区中。所以只需要在编译或构建时将其引用出来,组成一个临时的构建工作区满足构建的需求,而并不需要费心费力费事的把这些第三方库的代码整体搬移到大仓中。

公司的工蜂小组强力推荐使用 git 和开源界对标,我们也调整了自己的构建工作区的环境来适配这样的改变,比起使用 svn 来配置权限的,git 管理起来更加灵活而且通过不同的编译账户,即可灵活的组织编译依赖。

比起使用大仓单独文件夹来管理第三方依赖。使用虚拟文件夹对应社区的完整的 git 的仓库的好处是显而易见的。

2.3 统一编译 伏羲平台

其实相对于其他 BG,微信后端相比较是比较幸运的,我们一开始就站在了巨人的肩膀上,早在 2012 年就引入了 blade 后端构建系统,统一的编译系统解决了 C++ 在编译方面引起的巨大依赖问题,尤其是对于 C++ 缺乏完整的好用的包管理系统。早在 2014 年就推广使用的发布平台,统一了后端服务的发布流程。抓住了微服务的生产和发布两个一致性的大头,扫清了我们在流程一致性中的障碍。

但随着代码库越来越多,comm2``basic``mmcomm 等基础库变更越来越频繁,越来越多的开发在本机开发需要频繁的更新相关依赖。而且正是由于根深蒂固的单仓思想,开发者习惯于将整个编译工作区都检出到个人开发机上,但却发现这样做的收益是非常低的,因为编写一个微服务所需要提交的代码相比基础框架和依赖项是非常少的。

于是我们研究出了 patchbuild 统一编译。patch 是指把只需要提交到编译系统的部分通过补丁的方式提交到远端,build 指这些提交补丁的行为是用于构建二进制产物。于是统一编译系统提供了一套命令行工具给开发使用,其目的是希望用统一编译取代开发机的本地编译,实现本地开发,云端编译[^8]。

一个普通开发调试一个服务直接走下面的流程即可

在伏羲平台中维护了一套编译账户和代码库的信息,通过一套门户来决定某位开发者在当前的目录应该使用哪个编译账户来统一编译。管理者也可以通过这个网站来查看每日构建的使用情况,统计慢速编译和缓存使用情况。

统一编译对于开发测试的时的工具叫 patchbuild,正式上线的工具是 onlinebuild,其中 onlinebuild 不对普通开发直接开放。而必须使用 p6n、微信上线平台通过网页操作,这样就保证了只有特定的分支(master/PreMaster)才能上线到正式环境。

试想一下,如果使用大仓来管理所有微信后端的代码,将会带来非常多的问题。

无论大仓还是小仓,能够服务于一线业务开发的仓就是好仓。腾讯公司作为一个超大体量的公司,从组织上说如果不能服务好他们,影响的不仅仅只是核心技术团队的口碑,更还会影响公司业务的发展。整个微信后端也不是一开始就确定使用大仓或小仓的,还是在不断的业务发展中优先为一线开发提供研发效能的解决方案,然后再从方案中提炼出合适自己团队业务的方案的。

3 小仓的优秀实践(一致性篇)

首先必须承认一点大仓在一致性方面确实有独特的优势——你离开了仓库就不能写代码了,不能写代码了哪儿来的方式发布,特别是对于后端服务来说,使用大仓似乎是可以堵住胡乱发布、胡乱写代码的一种简单粗暴的方式。但我们认为众多小仓如果管理得当,一样可以做到可规模化,也一样可以做主干发布,这些优势并不能成为使用大仓的理由。

3.1 切换后端构建系统

微信后端编译系统于 2017 年将魔改版的 blade 切换到 bazel 构建工具。blade 实际上是一个 scons 脚本的生成工具,当我们通过命令行调用 blade 的,blade会根据所需要编译的 target 通过 python eval 的方式加载本次编译所需要用到的 BUILD 文件。BUILD 文件加载完成之后,blade 会根据 BUILD 文件当中所声明的target依赖关系和源文件列表生成一个 scons 的声明文件。bazel 和 blade 一个最大的不同的地方就在于,bazel 在处理依赖关系的时候并不区分 targe t级别和文件级别,也就是说在加载 BUILD 文件的过程当中 bazel 是直接计算文件之间的依赖。这使得 bazel 不需要等待整个编译依赖图全部都加载完成之后再开始执行编译动作。

切换整个 WXG 后端编译系统到 bazel 是一件收益很大,但困难重重的事情。但在遥远的 2017 年代,WXG 的代码仓库还是使用 SVN 的多仓模式,这导致如果要切换构建工具,必须整个仓库的迁移。而在业务快速迭代的同时,你根本就不知道迁移工具是否有 BUG,当时迁移的工程师提供了一个 blade2bazel 的转换工具,并制定了一套迁移方案,整个迁移涉及到所有 WXG 后端开发。

  1. 升级后端构建工具魔改版的 blade,继续魔改,如果发现有 BUILD_OF_BLADE 就使用此文件,否则就还是使用 BUILD
  2. 提供 blade2bazel 工具,尝试通过工具将 blade 版本 BUILD 转换为 bazel 版的 BUILD,备份原有的文件为 BUILD_OF_BLADE
  3. 通过每日构建找到不兼容 bazel 的 BUILD 文件或无法转换的文件,提示开发根据指引修改;
  4. 开发需要在一段时间持续维护 BUILDBUILD_OF_BLADE 两个构建脚本;
  5. 逐个 repo 迁移构建工具。直到所有的 repo 都迁移完毕,废除 blade。

当时所有的开发都秉承一种思想“所有的技术升级都不能阻塞业务的开发”,在 WXG 里面最重要的思想就是灰度、平滑升级、业务无感(此思想也适用于效能团队)。

也正是当时微信后端都启用分仓的模式,使得推进这进程没有那么痛苦。

在当时切换 bazel 时还有一次有意思的故事。支付团队以前由于 mmtenpay 仓库过大,一直都没有处理,就算是有升级工具的加持,依然在每日构建时成为阻塞的瓶颈。有一部分比较开明的开发,单独创建了一份 mmtenpay_bazel 的仓库来只是用 bazel 作为构建工具。试想一下,当时如果存在支付的整洁 GIT,所有的仓库根据业务系统来划分,那么就会有更加一致性的迁移过程,迁移的时间也就更加平滑,更省时。

3.2 支付整洁 GIT 实践

支付在 2016 年业务和开发人员规模迅速膨胀,导致 mmtenpay``mmpaygateway 两个 svn 的依赖混乱,构建复杂,甚至时不时的还引入环形依赖导致构建失败。最终痛定思痛在在 2018年决定启用单独 mmpay 新的 svn 仓库来结束混乱的局面,但 svn 依旧是权限管理非常生搬硬套,由于缺乏一个划分文件夹的标准,导致文件夹的层级越来越深,依赖查找越来越复杂。2018年底公司提出整体搬迁 git 战略,组织上加强领域驱动理论的学习,我们于是找到一套切实可行的划分 git 工作组仓库的方案。

首先我们需要理解领域驱动设计的思想:

如果按照领域驱动建模设计微服务是一个什么样的结构呢

一个微服务是不是应该单独一个仓库呢?事情当然不应该是这么简单的。由于微服务粒度太细,光支付目前就有数千个微服务,微服务之间的关联性是很强的,最终我们选择了一个领域内的作为一个工作组,每个子域作为一个代码仓库。

选择这样做的好处有非常明显的。

但我们也需要了解并不是所有业务系统都会选择领域驱动建模作为系统设计、代码开发的指导思想。所以不同的团队也许会有不同的划分小仓的方案,支付团队只能提供类似支付业务的一致性实践,不能代表所有的业务的开发。

构建一个基于领域驱动建模的需求管理系统也是一个困难的工程,如果忽略掉了这些先决条件,盲目凭直觉而简单地根据语言来划分文件,只用大仓甚至是全公司单仓来作为根本解决方案是不太合适的。

3.3 支付质量红线

公司在 2019 年的一个 KM 乐问引爆了大家对代码风格、规范的统一思考。支付团队在 2020 年中成立了代码委员会来统一管理微信支付代码红线,为了保证团队代码风格一致性和规范性,团队在公司 C++ 代码规范的基础上制定了支付质量红线,初步制定了要参与的代码仓库。

与公司不同的,支付对于代码中的硬编码、秘钥泄露是管理非常严格的,所以我们会需要更多的质量红线来防止一些安全事件的发生。包括但不限于:

在推行质量红线时,同样的我们对于任何一种发布或变更都会采取可灰度、可回退的方针。我们不能把质量红线这样一套门禁系统立即运用于所有代码工程,一刀切的政策搞不好就会上热搜,这样对于整个团队的士气的打击无疑是巨大的。同时我们也没办法一开始就保证流水线的设计是合理的,所以一定需要拿一些对质量要求特别敏感的仓库来小试牛刀。由于蓝盾的流水线的模板特性,我们确定只要我们设计好一套好用的流水线模板,就可以非常方便可扩展的应用到所有微信支付小仓项目中。

我们的行动计划是:

  1. 拿出一些典型的仓库作为设计质量红线的目标客户(如支付的公共组件 Xlib,基础支付中心某些新的仓库),设计支付红线流水线;
  2. 验证流水线是否可以拦截不规范的代码;
  3. 根据流水线设计流水线模板;
  4. 扩大流水线应用的仓库;
  5. 搜集流水线造成阻塞的反馈;
  6. 继续调整流水线策略并加大风格习惯的宣传;
  7. 将所有新增的仓库都自动添加质量红线堵住增量入口;
  8. 进一步灰度流水线到其他陈旧代码仓库。

支付线本身就有整洁 git 和非整洁 git 两种不同代码仓库,在进度到第 8 步时遇到了非常大的阻力。因为陈旧代码仓库的代码量之大导致蓝盾出错的几率增加,而蓝盾的客服人员告诉我们可以将 MR 关闭之后再打开重试流水线,这又造成了蓝盾的负载进一步增加。

我们庆幸在 2021 年整洁 git 的思想和代码编写模式已经非常深入人心,否则这样强推质量红线的实践可能就会成为灾难。在一个工具型产品应用的过程中,你会受到来自业务团队开发的各种阻力,只有不断的灰度打磨、精益的调整工具型产品的口碑,让各行各业的开发人员切实感觉到提升了他们利益,而不是让业务开发天天做一些修代码风格,整治遗留重构的漩涡中,这样才能让广大的开发人员接受研发效能工作,才能形成效能工具打磨的正反馈。至于说是否采用大仓,对于领导这一涉众而言只是一个解决一致性问题的方案,工具和管理才是提高效能的本质。

从组织上来说,小仓大仓只要能解决“一致性”的问题就是好的解决方法,无论是从后端构建系统、开源治理、仓库划分等组织上的诉求来看,大仓都并没有非常有利的优势。相反大仓的支持者通常使用“我们先用一个大仓规范起来,既然都在一个仓库了就理所应当的好治理了”这样一种话术来掩饰组织上在治理“一致性”方面的难题,仔细想想是不是规整分类的小仓库治理起来,比混乱不堪的文件夹乱飞的大仓,要简单容易呢。

4 分治

公司的开源治理的政策的核心是“一致性”。

而大仓的拥护者似乎把这个政策搞颠倒了,似乎只有使用大仓或未来让全公司都有几个为数不多的大仓才能解决上述问题,似乎把所有生产程序的原材料堆放在一起,才是解决效能的第一步,而这恰恰是目前和近 10 年来去中心化的大主流不相符合。

4.1 主干开发 vs 金丝雀 vs GitFlow

你看的没错,微信支付团队后端就是基于小仓做主干开发的。使用小仓并不影响使用主干开发,而且正式由于仓库是基于领域驱动划分的,所有的业务逻辑代码都在一个非常聚焦的仓库中进行。比起大仓做主干发开,小仓反而有更加灵活方便,主干开发并不是大仓的特有的,而是一种开发理念,只要是做业务开发的都可以选择使用主干开发,大仓做主干开发只是一种非常想当然的认为,但主干开发并不是大仓的必要条件。

我们认为业务代码的开发更适合于主干开发:

同时对于基础框架的开发,我们认为采用类似 Windows Insider 的金丝雀方案来确定分支更合适,(金丝雀方案比较类似于 GitLab Flow,只是比 GitLab Flow 更简化):

那什么情况下应该使用 GitFlow 呢,我们认为一些前端的或大量被未知世界依赖,强依赖未知世界源的对外开源框架,应该采用 GitFlow 模式:

由此可见主干开发、金丝雀开发、GitFlow 的选择并不应该是由公司的政策所导向,而是应该由业务特性、所选用的基础设施决定的,是需要不同定位的。而如果整个公司被划分成几个超级大仓,开发模式将会被完全锁死到看似比较简单的主干开发模式。至于腾讯会不会将来走到全公司一个大仓,大仓支持者却认为不一定有必要。这里关键是看什么样的项目使用什么样的仓库类型,而且仓库之间的组合是需要根据业务、构建系统、所处在的研发阶段动态配置的。

4.2 开源 vs 闭源

大仓的支持者一直认为将公共库放在大仓中可以解决依赖地狱的问题,而且会使得代码更容易的代码复用与分享。但此时我认为大仓小仓的划分不应该由代码的是不是公共库来划分,而是需要根据公共库所处在的研发地位和解决发方式来划分。例如(这两个例子都来源于真实的后端代码):

所以我们在选用小仓的同时必须考虑其代码解决的问题域,如果属于前者,我认为选择独立的小仓似乎更适合,而且这样的小仓也能更好的为开源界互动。

开源的目的是为了更好的协作,也是为了集合更多的智慧让某一份代码可以解决更多其专有域的问题,此时开源的收益是大于闭源的。闭源的目的是为专用系统更好的定制代码,使得业务开发更加能专注的开发其业务特性,而从依赖地狱、选择困难中解脱出来

为了保持开源小仓的灵活和良性互动,开源的小仓是在工蜂的 Fork 的一份副本,理想的做法如下:

  1. 从工蜂中直接克隆 github 的仓库;
  2. 在克隆的仓库中建立一个专用的私有分支,如(release/wx-build);
  3. 在私有的构建分支中添加适配内部构建的代码文件,如集成到微信后端编译系统,需要添加 BUILD 构建依赖脚本;
  4. 将私有分支信息配置到内部构建系统中,如配置到伏羲平台;
  5. 如果发现仓库有 BUG 直接在 wx-build 分支中快速修复并验证,需要贡献特性也可以如法炮制;
  6. 将 wx-build 中的更改拣选到 hotfix 分支中;
  7. 将此分支在外源(如 github)中提交 MR,跑外源的 CI 流水线;
  8. 若外源有更新,也可以将外源的更新 merge 到 wx-build 中。

大仓的拥护者似乎想到一种另外的解决方案,即公司专门组织一个开源代码委员会进行专门内源外源同步工作。在此我对此表示质疑:

如果使用大仓,可能会将某个开源设计的代码整体复制到一个文件夹中,或采用 sub_module 的方式引入,到时候维护者非常有可能只从整个大仓的角度思考此组件的特性,而忽略了在外源项目中此仓库的独立性。

另外类似这样的第三方库似乎只和生成产物的构建系统强相关,也不适合将其放在代码仓库中。代码仓库应该尽可能做到职责单一,使用单独的构建系统的产品(如微信后端的伏羲平台)可以非常好的解决仓库耦合度过重的问题。

由此可见,划分仓库的大小和工作组的分类并不是应该是有仓库代码的固有特性所决定的,过大过小都可能使得仓库的代码中丧失了其独立性,如果整个公司都或整个BG都是使用同一种划分模式,可能会出现:把代码放进入容易,但日后如何管理这个大仓陷入比较麻烦的局面。

4.3 灰度 灰度 还是灰度

有次有幸听到了 AMS 广告营销服务线的一次分享,分享主有幸提到了在半个月内将所有的模块由 gcc4 升级到 gcc7,并因此推断此单仓的方案能够非常有效的解决遗留债务的问题。但还是那句话,如果抛开业务的特性,很难说任何方案都具有普适性。在微信支付内(特别是收付退结核心产品),在几天内就将所有的模块升级 gcc 大版本是非常不能被接受的。

  1. 你无法保证 gcc 升级之后一些语法上的特性导致业务上行为的变更(如 C++11 去掉 string cow 特性);
  2. 你无法保证某些业务在 gcc 升级之后依然保证可以独立编译成功(如 C++11 修改了 map 的 insert 方法的函数签名);
  3. 你无法保证修改后的代码是可以兼容其他的第三方库的,其他的第三方库是不是需要同步升级;

支付这边的做法是:

  1. 在伏羲平台中新建一个编译账户;
  2. 对于基础库和依赖的组件配置低版本 gcc 的分支,并保留一段时间;
  3. 业务代码使用这个编译账户来构建低版本 gcc 的产物,并使用正常的编译账户构建高版本 gcc 产物;
  4. 逐步迁移仓库到高版本 gcc,并保持可回退编译的能力;
  5. 当所有仓库都迁移到高版本 gcc 一段时间后再废弃掉升级时所用的编译账户。

无论是数据迁移、库迁移、模块迁移,支付系统由于自身业务的定位,绝对不允许出现不可灰度、不可回退的方案,支付定位是金融级系统的可靠性保证,质量安全是支付生命线。

开源治理的思想应该是指定大的方针方向(比如我们要构建开源协同的文化氛围 Oteam,构建开源协同的工具工蜂、Tapd、iWiKi等),而不是特别钦定必须使用哪一中具体的特定解决方案。解决方案应该是“接地气”的和业务相关的一组策略的组合,而非强调必须使用这一组合中某个特定的工具,最终寄希望这一特定工具来解决所有问题。

结语

把上文的要论述的部分整理成一份脑图分享一下。

没有哪一种分仓方案是完美的方案,也许 Google 在它的历史而长河中选择了大仓方案,Amazon 基于自身的基因选择了小仓方案。只有辩证的看到大仓或小仓,才是最科学的办法。然而只有一个空想沉思的口号而没有具体实践的指导计划就是脱离了实事求是的魂,没有去调研一线业务开发的疾苦就是脱离了群众路线的根、不是根据历史的发展规律而盲从 Google 的行为标准就是脱离了独立自主的本。只有从实践出发,结合公司业务发展的客观规律,才能最终发掘出一份接地气的解决方案。这也是我们对待技术与业务的最融洽的方式。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8