本文会尝试回答下面几个问题:

  1. 什么是单体仓库 (mono-repo)?
  2. 为什么 Google/Facebook/Bilibili 采用单体仓库?
  3. 单体仓库 (mono-repo) 和多仓库 (multi-repo) 分别解决了哪些问题?
  4. 单体仓库 (mono-repo) 和多仓库 (multi-repo) 在解决问题的同时又引入了哪些问题?

一、为什么要区分单体仓库和多仓库?

在介绍单体仓库和多仓库前,我们先了解一下应用的类型,了解一下为什么要区分单体仓库和多仓库。

1. 单体应用(monolith)

在早期,实际上是不用区分单体仓库和多仓库的,因为在早期的时候一个应用就把所有功能打包了。哪怕使用多个仓库也是根据公共模块方式来区分,仓库数量也是比较少的。这时候的应用我们一般叫他单体应用。主要的特点就是一个程序就打包所有功能,全家桶。

这样的开发模式有很多优点,如:

  1. 易于理解项目整体。开发人员可以把整个项目加载到本地的 IDE 当中,进行 code review,方便开发人员把握整体的技术架构和业务目标。
  2. 易于集成和部署。所有的代码在一个仓库里面,不需要特别的集中管理和协调,也可以直接在本地部署调试。
  3. 易于重用。所有的代码都在一个仓库中,开发人员开发的时候比较容易发现和重用已有的代码,而不是去重复造轮子。
  4. 易用重构。开发人员(通过 IDE 的支持)容易对现有代码进行重构,可以抽取出一些公共的功能进一步提升代码的质量和复用度。
  5. 易于规范代码。所有的代码在一个仓库当中就可以标准化依赖管理,集中开展 code review,规范化代码的风格。

但是随着单一应用功能的拓展,应用仓库无限制变大也带来了很多缺点,如:

  1. 开发效率大幅降低。所有的开发人员在一个项目改代码,递交代码相互等待,代码冲突不断。
  2. 代码维护性变差。随着功能以及代码量的大幅增加,所有代码功能耦合在一起,新人不知道何从下手。
  3. 构建时间过长。任何小修改必须重新构建整个项目,这个过程往往很长。
  4. 稳定性太差。任意一个功能出现问题,可以导致整个应用挂掉。
  5. 扩展性太差。所有功能都在一起,无法简单的横向扩展,容易受限于物理机器的配置。

2. 微服务应用(micro-service)

既然单一应用的问题是由于后期功能过多,应用复杂度上升产生的。那是否可以通过将单体应用拆解成无数的小应用来避免这些问题呢?

实际上后面出现的微服务这个概念就类似于上面的思路,通过将一个大型应用拆分成无数个服务。每个服务有自己的代码文件,单独部署,然后共同组成一个应用程序。

需要注意的是这里的 "微" 不是指的代码行数,而是说服务的功能限定到单个功能。

微服务相比单体应用最大的好处是可以独立的开发测试部署和扩展。

既然单体应用一般采用单体仓库,那么微服务的代码仓库又是如何组织的呢?实际上关于这点网络上讨论的不是很多,基本上就是一个服务一个仓库。

我也一直以为这样是微服务的最佳实践。但是 bilibili 的源代码告诉了我们另一个选择,单体仓库(mono-repo

二、单体仓库和多仓库

实际上,针对微服务代码的仓库组织,业界一直有两种主要的实践:

  1. 一种是多仓库(multi-repo),在微服务中就是每个服务一个源码仓库。
  2. 另一种叫单体仓库(mono-repo),虽然应用采用的微服务架构,但将所有源码放在同一个仓库。

从左到右依次为:单体应用单体仓库(monolith),微服务多仓库(mono-repo),微服务单体仓库(mono-repo)。

首先需要肯定的是,单体仓库和多仓库两种方案能同时存在,一定是各有利弊的,希望大家不要将自己锁定到一种方案上。软件开发中没有银弹,没有绝对的好与不好之分,选择适合自身团队的管理方案就好。

1. 多仓库(multi-repo)

多仓库为我们带来了如下好处:

  1. 每一个服务都有一个独立的仓库,职责单一。
  2. 代码量和复杂性受控,服务由不同的团队独立维护、边界清晰。
  3. 单个服务也易于自治开发测试部署和扩展,不需要集中管理集中协调。
  4. 利于进行权限控制,可以针对单个仓库来分配权限,权限分配粒度比较细。

但同时,多仓库也存在着以下的问题:

  1. 项目代码不容易规范。每个团队容易各自为政,随意引入依赖,code review 无法集中开展,代码风格各不相同。
  2. 项目集成和部署会比较麻烦。虽然每个项目服务易于集成和部署,但是整个应用集成和部署的时候由于仓库分散就需要集中的管理和协调。
  3. 开发人员缺乏对整个项目的整体认知。开发人员一般只关心自己的服务代码,看不到项目整体,造成缺乏对项目整体架构和业务目标整体性的理解。
  4. 难以保证版本一致性。各个项目单独管理依赖,难以将所有项目使用的框架或组件版本保持一致。
  5. 项目间冗余代码多。每个服务一个服务一个仓库,势必造成团队在开发的时候走捷径,不断地重复造轮子而不是去优先重用其他团队开发的代码。

2. 单体仓库(mono-repo)

单体仓库的优点:

  1. 易于规范代码。所有的代码在一个仓库当中就可以标准化依赖管理,集中开展 code review,规范化代码的风格。
  2. 易于集成和部署(CICD)。所有的代码在一个仓库里面,配合自动化构建工具,可以做到一键构建、一键部署,一般不需要特别的集中管理和协调。
  3. 易于理解项目整体。开发人员可以把整个项目加载到本地的 IDE 当中,进行 code review,也可以直接在本地部署调试,方便开发人员把握整体的技术架构和业务目标。
  4. 易于保证版本统一。同一仓库下的微服务依赖的外部框架、代码库将能保证版本统一。
  5. 易于重用。所有的代码都在一个仓库中,开发人员开发的时候比较容易发现和重用已有的代码,而不是去重复造轮子,开发人员(通过 IDE 的支持)容易对现有代码进行重构,可以抽取出一些公共的功能进一步提升代码的质量和复用度。

单体仓库的缺点:

  1. 项目代码公开。单体仓库基本放弃了对读权限的限制,开发人员可以接触到项目所有代码。
  2. 管理权限复杂。对于写权限,单体仓库也是有着自己的解决方案,比如 OWNERSCODEOWNERS 等,但相比多仓库还是差了一些。
  3. 降低服务独立自治能力。单个服务的开发测试部署和扩展,需要集中管理集中协调,降低了微服务单个服务的自治程度。
  4. 代码量和复杂性不受控。随着公司业务团队规模的变大,单一的代码库会变得越来越庞大复杂性也呈极度的上升,容易受团队能力及开发流程等影响导致结果不可控。
  5. 对管理工具和手段要求较高。想要玩转单体仓库,一般需要独立的代码管理和集成团队进行支持,加上配套的自动化构建工具来支持。某些方面已经出现了开源的方案,比如 Google 自研的面向单体仓库的构建工具 Bazelhttps://bazel.build/  Facebook  Buckhttps://buck.build/ 。但还是需要团队进行整合。

世界上采用单体仓库管理源码的公司实际并不少,比如: GoogleFacebookTwitter 这些互联网巨头。可以看到,虽然这些公司系统庞大、服务众多,内部研发团队人数众多,但是采用单体仓库也很好的解决了业务需求。

三、总结

微服务架构并不是主张所有的东西都要独立自治,至少代码仓库就可以集中管理,不要让先入为主的观念影响你的判断,根据实际需要选择最合适的方案。初创公司的话,在公司早期并且服务不是特别多的情况下,采用单体仓库比较合适。这样也容易养成团队统一的代码风格,过早的分而治之容易导致团队各自为战,代码风格散乱等问题。

四、参考





Content Menu

  • No labels