前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >125G 代码,10 秒内准备完成:CNB 刷新 AOSP 克隆效率

125G 代码,10 秒内准备完成:CNB 刷新 AOSP 克隆效率

原创
作者头像
志航
修改于 2025-04-25 07:21:04
修改于 2025-04-25 07:21:04
2680
举报
文章被收录于专栏:cnb.coolcnb.cool

了解更多,可以访问我们的官网 👉 https://cnb.cool

最近我们团队开始进行一个新项目,需要基于 Google 的 Android 14 系统源码来定制 ROM。我们需要下载 AOSP(Android Open Source Project)的代码进行开发。AOSP 是一个提供了支持移动设备和嵌入式设备开发的完整技术栈,可以用来开发智能手机、平板电脑、车载系统、智能电视等各种设备。AOSP 项目源码规模很大,占用磁盘 124.44GB,超过 1400 仓库。

“大佬,可以打一个有最新代码的 ROM 么? 我测试一下新功能”“可以,我去操作一下, 大概 1 个半小时后你再来拿”“1个半小时这么久?”  “对啊, 拉代码就得 20 分钟, 还得跑测试和编译….”

团队在开发中,经常会出现类似的对话,我们发现团队在开发这种超大型仓库时,面临着多方面的问题和困难:

  • 代码拉取耗时长:由于代码仓库很大 (125GB)导致拉代码很慢,通过 repo 下载代码需要接近 20 分钟。
  • 存储空间占用大:团队开发很多,代码占据了每个开发电脑的 125GB 空间。
  • 效率低:当前团队的 CI 系统,使用 repo 来管理多仓的 AOSP,容易在代码 clone 阶段就挂掉。并且流水线每次代码准备的时间都得 20 分钟,构建耗时长达 50 分钟,每次编译出包都超过 1 小时,加上其他跑测试的时间,总耗时就更长了。要是遇上高并发,编译效率更一言难尽
  • 并发构建受限:机器上拉下来的代码,每次只能供一个流水线操作,强行并发会造成冲突和污染,多个流水线只能串行跑,效率极低,构建时间随着流水线增加而线性增加。

为了加速团队的开发速度,解决流水线每次需要全量 clone 代码的耗时问题,团队还购置了一台本地的高性能机器,专门服务于代码编译。每次有开发同学需要编译的时候,就去该机器上增量拉代码,然后编译,这样可以尽可能使用机器上的代码缓存,然而,这种方法仍存在局限性:

  • 无法满足并行运行多个编译任务的需求
  • 大部分时间机器处于闲置状态,资源利用率

面对这些持续存在的挑战,我们意识到需要从根本上解决代码clone的速度问题。这不仅关系到开发效率,还直接影响着我们的 CI/CD 流程和资源利用。因此,我们开始探索各种可能的优化方案,希望能找到一个既能显著提升 clone 速度,又能满足我们复杂开发环境需求的解决方案。

我们首先研究了几种常见的代码 clone 优化技术:

  • 浅 clone (shallow clone): 虽然可以减少下载的历史提交数量,但无法获取完整的代码历史,不利于代码追溯和版本管理,并且速度提升不明显。
  • 稀疏 clone (sparse clone): 虽然 repo 工具的按仓库粒度选择性克隆功能在管理大量仓库时提供了灵活性,但也带来了复杂性、依赖管理、版本一致性等方面的挑战。在使用这种方法时,团队需要充分考虑这些缺点,并制定相应的策略来管理和协调开发工作。
  • 部分 clone (partial clone): 虽然可以只下载文件元数据,但在需要实际文件内容时仍需额外下载,可能导致意外的延迟。

这些方法虽然在代码速度或者存储空间方面有所改善,但都无法全面解决我们面临的问题。特别是在处理超大型仓库时,这些方法往往显得力不从心。

主要影响我们开发的核心 2 大矛盾点:

  • 代码 clone 速度太慢
  • 高并发下无法复用代码缓存复用,容易造成代码冲突和代码污染

解法出现


我们开始关注腾讯新发布的 CI 工具 : CNB

Cloud Native Build 云原生构建 -- https://cnb.cool

图片
图片

CNB 基于 Docker 生态,对环境、缓存、插件进行抽象,通过声明式的语法,帮助开发者以更酷的方式构建软件。

在这里可以托管代码和制品、项目开源以及参与社区协作,通过云原生构建可以更快的构建软件,使用云原生开发,告别复杂的本地开发环境,一键唤起云上工作空间。

其中他有一个非常重要的特性:“读秒克隆” , 就是可以在数秒内完成代码准备,无视仓库的大小,并且是并发安全的,高并发场景下也是如此。

这个特性对于我们团队非常重要,如果真的可以秒级 clone 准备好代码,仅仅在代码 clone 上就可以给我们团队代码大约 200 倍的速度提升。我们马上开始用 125GB 的 AOSP 代码仓库进行测试。

迁移到 CNB 也很简单

  1. 把代码仓库从使用 repo 管理,迁移到单仓 monorepo 来管理,用平台自动化脚本同步了历史提交记录,然后把代码仓库 push 到 CNB 的 git 仓库上
  2. 用 ubuntu 18.04 作为基础镜像,准备一个可以用来构建 AOSP 的 Dockerfile,作为构建容器
  3. CNB 的配置是声明式的,主打"Everything as Code",通过配置文件来描述流水线,非常简单清晰,与 Git 代码仓库同源管理
代码语言:yaml
AI代码解释
复制
build_config: &aosp_build_config
  runner:
    cpus: 64
  docker:
    build: .ide/Dockerfile
    volumes:
      - out:copy-on-write
  stages:
    - name: build
      env:
        BUILD_HOSTNAME: cnb-build
        shell: |
          source build/envsetup.sh
          lunch aosp_arm-eng
          make -j64
      script: bash -e -c "${shell}"

# 测试并发 6 流水线测试
main:
  push:
    - *aosp_build_config
    - *aosp_build_config
    - *aosp_build_config
    - *aosp_build_config
    - *aosp_build_config
    - *aosp_build_config

测试数据:

代码仓库地址:https://cnb.cool/aosp/monorepo

1、首次启动,无缓存拉取代码,直接构建

  • 拉取代码:16m 52s, 124.44GB
  • 构建耗时:46m 4s

2、第二次以后,带有代码缓存,并且并发构建

真的 秒级 clone , 3.8s !!!124.44GB !!!

且并发构建依然可以命中缓存!

  • 拉取代码:3.8s , 124.44GB
  • 构建耗时:1m 30s

下面截图就展示了并发 6 条流水线时的真实耗时:

图片
图片

性能对比

代码准备

编译

未使用 CNB,无缓存加速

16m52s

46m4s

使用 CNB,缓存加速

3.8s

1m30s

使用 CNB 后,团队在代码克隆方面的需求得到了显著改善,传统的浅克隆和部分克隆等优化措施变得不再必要,因为 CNB 提供了快速且全面的代码克隆功能。这一优势使得 CNB 在与其他 CI 系统的比较中脱颖而出,有效解决了团队在处理超大型仓库时所面临的代码克隆和构建缓存问题。

秒级克隆原理


为什么 CNB 可以做到秒级克隆这么快?

CNB 使用 git-clone-yyds 插件实现的秒级克隆, 出于好奇,我阅读了代码,以了解其内部实现的机制。

代码开源地址:https://cnb.cool/cnb/cool/git-clone-yyds

1、缓存机制加速代码拉取

CNB 上代码首次被 clone 下来以后,会被持久化缓存到构建机的母机上。往后启动流水线只需要增量更新代码,这个过程比全量 clone 代码要快得多,可以在数秒内完成( 如 AOSP 125GB -> 3 秒完成)。

代码缓存能显著减少启动流水线时准备代码所需的时间。然而,如果仅仅实施代码缓存,其本质与在开发机上拉取代码并构建相似,存在明显缺陷。当团队中多人同时开发,需要并发启动多条流水线时(如同时进行端到端测试(e2e)、单元测试、构建多个包等),这些并发构建操作会同时修改工作空间(workspace)中的多个文件。这种情况下,被缓存的源代码可能被污染,从而影响流水线的幂等性,导致流水线失败,或者产生不可预测的结果,增加了维护成本,并且还有可能会出现 cache miss 的情况,导致不得不重新全量 clone 代码

对于上面这种情况,大部分的 CI 系统会采用集中解决方案,如:

  • 增加锁机制,串行化流水线,同时只能有一个流水线可以运行,其他流水线需要排队等待。这种方式虽然可以解决问题,但会显著增加流水线的运行时间,降低系统的吞吐量,硬件资源利用率低,用户等待时间长。
  • 把代码缓存放到分布式存储中,这样可以避免代码被污染,如 bazel 的 remote cache。但是这种方式需要额外的成本:维护分布式存储,网络传输成本与耗时,数据一致性等问题,并且不能替代所有的场景

那么,CNB 是如何做到在高并发中使用代码缓存的呢?

2、Copy-on-Write 机制

从计算机角度来看,git 代码克隆缓存和构建缓存等操作本质上是典型的文件独占问题。传统上,这些文件在同一时间只能服务于一次构建,限制了并发性能。为了解决这个问题,CNB 采用了  Copy-on-Write (简称 CoW)机制。

Copy-on-Write(CoW)是一种优化策略,允许多个进程共享同一份资源,直到需要修改时才创建副本。这种机制大家并不陌生,在 Docker 上就有使用。Docker 利用 OverlayFS(一种联合文件系统)来实现 CoW。OverlayFS 能够将多个目录层叠在一起,形成统一视图,这使得多个容器可以共享同一个基础镜像,只有在需要修改时才在特定容器中创建独立的可写层。这种技术不仅优化了存储使用,也是 Docker 容器瞬间启动的原因。

下图简单描述了 OverlayFS 的运作原理,其中:

  1. file1 是没修改的,直接从 lowerdir 透明地呈现在 merged 层。
  2. file2 在 upperdir 中被修改。当读取 file2 时,会看到 upperdir 中的修改版本。
  3. file3 在 upperdir 中被删除。虽然 lowerdir 中仍然存在 file3(在 upperdir 中它被标记为 "Whiteout" 状态),但在 merged 层中它是不可见的。
  4. file4 是新增的文件,直接存在于 upperdir 中。

通过结合 CoW,CNB 能够有效地解决文件独占问题,允许多个并发构建共享相同的基础文件系统,同时保持各自的独立性。这不仅提高了系统的并发性能,还确保了每个构建环境的隔离性和一致性。

这就是 CNB 同时解决代码克隆速度慢和高并发下缓存复用冲突的关键所在。

3、git-clone-yyds 插件秒级 clone 原理

3.1、git-clone-yyds 工作流程图

git-clone-yyds 本质上是运行在母机上的一个 docker 容器,他通过  volume  把代码缓存挂载到工作区 (workspace)下。下图展示了 CNB 的 git-clone-yyds 在准备工作区的流程。

  1. git 代码缓存初始化:如果是首次拉代码,需要运行  git init  和  git fetch  进行代码的拉取,如果是已存在  .git  目录,则直接使用  git fetch  对  .git  进行更新
  2. workspace 代码准备,使用  OverlayFS  对  .git缓存  进行复制,瞬间创建副本。通过  mount -t overlay  的方式,把代码缓存挂载到 lowerdir,并且建立一个空的 upperdir,然后 merged 就是最终在流水线看到的 workspace 的文件视图。
  3. 在 merged 文件夹上进行 checkout、build 等操作,基于 CoW 的特性,可以实现当需要写入时,从 lowerdir 的 cache 上 copy 出来再写入,如果只需要读,直接从 lowerdir 读取,这样就能保证在并发场景下的代码隔离。
  4. 构建结构后,删除副本。
图片
图片

3.2、从文件系统挂载上观察 git-clone-yyds

从文件系统挂载上看,可以更清晰的看到 CNB 是如何使用 git-clone-yyds 实现秒级的代码 clone 的。

图片
图片

当母机上启动构建容器时,会将 /data/git/{group}~{repo}/cache 作为只读的 lowerdir 层,通过 OverlayFS 挂载到容器的/workspace目录下。这个 /workspace 目录具有 Copy-on-Write 特性,任何对文件的修改都只会影响容器自己的 upperdir,不会改变底层 lowerdir 的代码缓存。

这样就实现了代码的秒级拉取,同时保证了并发场景下的代码隔离。

3.3 并发构建场景下的缓存

在同一个代码仓库并发多个流水线构建时,母机上只会存在一份代码缓存,。每个流水线会通过自己的 git-clone-yyds 准备 workspace 代码,通过 OverlayFS 建立 CoW 文件夹等。同一个代码仓库不同的流水线的 workspace 是通过 pipeline id 文件夹实现隔离的,但是 workspace 底层的  lowerdir  是同一个。这样就保证了在并发启动多条流水线的时候,可以并发使用缓存。母机上只需要一份缓存代码,并且最重要的首次准备好代码缓存以后,可以在数秒内完成代码准备。高并发场景下,也是如此

图片
图片

总的来说, git-clone-yyds 的性能非常高

  • 时间复杂度:O(1),无视仓库大小,并发安全。
  •  空间复杂度:O(1),存储空间不随并发数增长。

数据


从 CNB 系统全局性能监控来看,git-clone-yyds 非常快, git clone 的时间稳定在 10s 以下,大部分项目都在 3~6s 时间准备好工作区,速度非常快!

图片
图片

更多的玩法


当然这里只是 CNB 对克隆时间的加速,如何将 AOSP 的编译时间从46分钟显著缩短至仅1分钟?后面我们再深入探讨 CNB 如何通过使用 CoW 和 volume 实现编译缓存的具体实现。

CNB 系统通过创新的 git-clone-yyds 插件和 Copy-on-Write (CoW) 机制,不仅解决了大型代码仓库的克隆速度和并发构建问题,还为我们开启了更多可能性。

了解更多,可以访问我们的官网 👉 https://cnb.cool

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
Swift 代理模式
在iOS开发中,无论是 Objective-C 还是 Swift ,Delegate 有着具足轻重的位置,如TabelViewDelegate 与 TableViewDataSource。
零式的天空
2022/03/23
5470
Protocol与Delegate 使用方法详解你要知道的KVC、KVO、Delegate、Notification都在这里
你要知道的KVC、KVO、Delegate、Notification都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本系列文章主要通过讲解KVC、KVO、Delegate、Notification的使用方法,来探讨KVO、Delegate、Notification的区别以及相关使用场景,本系列文章将分一下几篇文章进行讲解,读者可按需查阅。 KVC 使用方法详解及底层实现 KVO 正确使用姿势进阶及底层实现 Protocol与Dele
WWWWDotPNG
2018/04/10
1.1K0
Protocol与Delegate 使用方法详解你要知道的KVC、KVO、Delegate、Notification都在这里
窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用
  协议与委托代理回调在之前的博客中也是经常提到和用到的在《Objective-C中的委托(代理)模式》和《iOS开发之窥探UICollectionViewController(四) --一款功能强大的自定义瀑布流》等博客内容中都用到的Delegate回调。说到协议,在Objective-C中也是有协议的,并且Swift中的协议和Objc中的协议使用起来也是大同小异的,在Java等现代面向对象编程语言中有接口(Interface)的概念,其实和Swift中或者Objc中的Protocol(协议)是一个东西。
lizelu
2018/01/11
4K0
窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用
Swift| 基础语法(四)
总结下 swift下的基础语法,里面涉及到:常量&变量、Swift中的数据类型、逻辑分支、循环、字符串相关、数组和字典、方法的书写调用等内容,考虑到阅读体验分多篇来展示,希望对大家学习swift有所帮助,同时也是对自己的一个总结。
進无尽
2018/09/12
1.5K0
Swift| 基础语法(四)
iOS开发之通过代理逆向传值
在iOS开发中,传值是几乎每个App都会用到的,对于传统的顺向传值应该说是比较简单的,但是逆向传值往往会用到代理模式来实现,很多同学在这一块有迷惑,迷惑的不是怎么逆向传值,而是不理解代理模式,下面就来分析一下,如何理解和快速上手代理模式,并以一个简单的逆向传值为案例,看看代理模式是不是很难。 一、代理模式中的几个概念 讲代理模式之前,首先需要弄清楚两个概念:被代理对象和代理对象。并且需要知道它们之间是靠协议关连起来的。 1、被代理对象 被代理对象往往就是真正有做事意图的那个对象,比如卖房子案例中的想卖房子的
YungFan
2018/05/03
7750
iOS开发之通过代理逆向传值
iOS代理,通知,block的用法及不同
在开发过程中,总是遇到不同页面之间传参问题,代理,通知,block 都可以实现这种简单功能,但是有时候都是根据自己的熟悉程度选择使用的方法,并没有深度的认识之间的用法和不同,在此系统的整理下。
honey缘木鱼
2019/04/25
1.9K0
iOS代理,通知,block的用法及不同
设计模式 -- 代理模式
在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务
xy_ss
2023/11/22
1590
设计模式 -- 代理模式
iOS开发-KVO与KVC、通知与代理模式
解释意思:通过一个key去找到对应的编码 初学者很同意搞混与KVO的区别,我提个意见最后是个C,想它就是词,于什么有关系:词典(NSDictiontroy)
孙寅
2020/06/02
6960
iOS - MVC与MVP的差异 (一)
拿我在以前写的一篇文章中作例子 iOS-NSArray与Model模型 该篇文章中Model层数据通过UITabelView的delegate & dataSource 方法
Wilbur-L
2021/11/12
8570
iOS中storyboard故事板使用Segue跳转界面、传值
在iOS的开发过程中,不可避免的要设计界面,在android中有xml设置界面和直接使用java代码设置界面控件两种方式,在之前的ios开发中也是类似的有xib文件设置界面及用代码直接设置控件两种方法,但后来又出了一种方式,就是storyboard故事板子,其实storyboard和xib文件很像,最大的不同之处在于一个xib文件对应一个ViewController视图控制器,而storyboard对应多个,基本一个应用只需要一个storyboard就可以了,不再需要为每个控制器创建一个xib文件,从这点上来说,还是很方便的,在storyboard中查看各个界面的跳转也很方便,但之前一直使用xib进行开发,对storyboard的使用不太熟悉,今天好好学习了一下其中的界面跳转和传值,用到了Segue这个东西,这里借着例子说明一下。
Cloudox
2021/11/23
1.8K0
iOS中storyboard故事板使用Segue跳转界面、传值
IOS开发之自定义Button(集成三种回调模式)
  前面在做东西的时候都用到了storyboard,在今天的代码中就纯手写代码自己用封装个Button。这个Button继承于UIView类,在封装的时候用上啦OC中的三种回调模式:目标动作回调,委托回调,Block回调。具体的内容请参考之前的博客:“Objective-C中的Block回调模式”,“Target-Action回调模式”,“Objective-C中的委托(代理)模式”。在接下来要封装的button中将要用到上面的知识点。之前在做新浪微博中的Cell的时候用到了Block回调来确定是那个Cel
lizelu
2018/01/11
1.3K0
IOS开发之自定义Button(集成三种回调模式)
iOS学习——页面的传值方式iOS----KVC和KVO 详解
在iOS开发过程中,页面跳转时在页面之间进行数据传递是很常见的事情,我们称这个过程为页面传值。页面跳转过程中,从主页面跳转到子页面的数据传递称之为正向传值;反之,从子页面返回主页面时的数据传递称之为反向传值。
mukekeheart
2019/09/29
1.9K0
iOS学习——页面的传值方式iOS----KVC和KVO 详解
Swift:delegate
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
菜菜不吃蔡
2019/11/03
1.2K0
wkwebview加载完成_【Swift】WKWebView与JS的交互使用
近日,有朋友问我关于WKWebView与JS的交互问题,可我之前一直使用的是UIWebView,也不曾做过WKWebView的交互啊!接下来大家一块学习下WKWebView是怎么实现原生代码和JS交互的。
安德玛
2022/03/08
6K0
iOS-代码规范
利用上周的业余时间把这篇规范整理了出来,我会将这篇规范作为我们iOS团队的代码规范,并且还会根据读者的反馈,项目的实践和研究的深入做不定时更新,还希望各位朋友看了多多指正和批评。
用户1740424
2018/07/23
2.3K0
iOS-代码规范
Swift基础语法(四)
在Swift5之前,我们一般是采用上面的方式来处理异常,在Swift5之后,苹果推出了一个Result枚举,Result枚举可以更加优雅地去处理异常。
拉维
2020/07/06
4.2K0
Swift基础语法(四)
史上最全的iOS之访问自定义cell的textField.text的N种方法
问题背景:自定义cell中有一个UITextField类型的子控件。我们经常要在tableView中拿到某个cell内textField的文本内容进行一些操作。比如某些app的注册界面就是以tableView的形式存在的,注册时往往需要注册姓名、昵称、邮箱、地址、联系方式等信息。然后点击注册或者提交,这些信息就会被提交到远程服务器。有人说,注册页面就那么固定的几行cell,没必要搞得那么复杂,完全可以用静态cell实现。但还有一些情况,当前页面的tableView的cell的行数是不确定的(比如当前页面显示多好行cell由上一个页面决定或者由用户决定),这种情况下不太适合使用静态cell。也不能够通过分支语句的方式一一枚举出各个case。所以需要一中通用的动态的方法。那么我们怎么在tableView中准确的拿到每一行cell中textField的text呢?以下我将要分四个方法分别介绍并逐一介绍他们的优缺点,大家可以在开发中根据实际情况有选择的采用不同的方法。 如下图,就是我之前开发的一个app中用xib描述的一个cell,当用户点击“注册”或者“提交”button时候,我需要在控制器中拿到诸如“法人姓名”这一类的信息:
VV木公子
2018/06/05
7.1K0
史上最全的iOS之访问自定义cell的textField.text的N种方法
关于OC代理的问题:self.delegate = self
于是心中产生了不少疑问,为什么会这样写?这样写是否是正确的?带着这些疑问,我去查找了一些资料并进行了整理,希望可以分享给大家。
用户5521279
2021/02/09
1.5K0
关于OC代理的问题:self.delegate = self
自定义转场详解(一)
前言 本文是我学习了onevcat的这篇转场入门做的一点笔记。 今天我们来实现一个简单的自定义转场,我们先来看看这篇文章将要实现的一个效果图吧: 过程详解 热身准备 我们先创建一个工程,首先
Scott_Mr
2018/05/16
9800
iOS_scrollView title and line view, 滚动的标题和线
废话不多说, 直接上代码 // // MOTitleLineView.h // MOTitleLineView // // Created by 莫小言 on 15/12/17. // Copyright © 2015年 莫小言. All rights reserved. // #import <UIKit/UIKit.h> // title 对齐type typedef NS_ENUM(NSUInteger, MOTitleLineViewType) { MOTitleLineViewTy
mikimo
2022/07/20
9820
iOS_scrollView title and line view, 滚动的标题和线
推荐阅读
相关推荐
Swift 代理模式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档