字节跳动 Mobile DevOps 工程实践

新闻资讯   2023-06-07 15:01   72   0  


演讲人 | 李腾飞
整理 | Penny
策划 | 丁晓昀,孙瑞瑞

随着移动互联网的快速发展,各大厂都拥有自己的超级 APP,为了尽快的满足用户需求,研发同学需要在这些超级 APP 上快速完成功能迭代的同时,还要保证质量。以抖音举例,上百名来自不同部门的研发同学每周都会向抖音工程中集成各自的功能(支付、feed 流、电商等),为了解决整个研发过程中的问题,字节内部自研了一套叫 Bits 的 Mobile DevOps 系统,用于支撑字节内部所有 APP 的 CI/CD 流程,保障了抖音、头条等头部 APP 的单周版本迭代。

本文整理自 QCon 2022 北京站上李腾飞的演讲《字节跳动 Mobile DevOps 工程实践》,主要给大家介绍 Bits 系统从 0 到 1 过程中的一些工程经验和想法,通过 MR (Merge Request,合并请求)从创建到合入的过程展现整个研发流程的细节和功能。

背景

我于 19 年加入字节,一直在 DevOps 这一领域工作。今天很高兴能与大家分享我们的工作。自 17 年开始,字节内部着手从零开始开展 Mobile DevOps,本次演讲分享该平台在公司内部的使用情况。本次分享分为三个部分:

  • 第一部分介绍项目背景,即我们公司内部面临的问题及为何决定着手解决此问题;

  • 第二部分是我们基于该背景所考虑的解决方案,以及采取哪些方法解决对应问题;

  • 第三部分则介绍在公司内部实际业务操作中该平台的使用现状。

下图是当时项目当时的现状。


这个项目的背景是在字节内部 APP 快速发展的情况下,面临业务高速发展的挑战。在新项目立项时,无法从工程架构等方面充分准备,导致代码冗余问题,例如西瓜视频直接从头条仓库复制代码,使得头条出现 bug 后,西瓜视频也需要再次解决。此外,不同 APP 内的组件耦合问题也会造成安全和性能方面的困扰。为此,字节围绕组件化做了很多工作,使得在写业务时变得更加轻松。


问题

字节 Mobile DevOps 项目面临以下具体问题。

  1. 基础库混乱不堪,出现了重复造轮子的情况。通常库的维护和迭代流程也缺乏清晰的规范。因为在字节内部,过去的分工划分为头条线和互娱线,而互娱线的代表是抖音。由于工程和公司内部分工的原因,以及为了满足业务快速迭代的需求,我们团队与其他团队之间的沟通很少。这导致了同样一个功能可能在头条和抖音两个团队中同时开发。我们希望能有一个公共的地方来解决这个问题。


  2. 团队合作中代码依赖和冲突会逐渐增多,协作变得困难。对于一个小的 APP,20-30 人之间的协作还比较容易,但是对于像抖音这样的应用,单端有 1000 多名 RD,互相协作的难度就会增加。我们需要解决沟通问题,以及在各自迭代需求时解决代码冲突问题。例如,抖音里面有商城和 feed 流这两个功能,完全分属于公司内部的两个团队。我们需要在一个产品上进行集成来解决这个问题。


  3. 代码逻辑的相互影响,测试效率较低,难以保证质量。举个例子,如果我在测试 feed 流的功能时只测试自己的功能,测试可能没有问题。商城的功能则会在它自己的 feature 分支上测试,也可能没有问题。但是当我将 feed 流和商城的功能集成到主干分支上时,可能会出现问题。因为集成上去的功能没有经过 QA 测试,只能在回归阶段进行测试。如果测试不到问题,就会导致线上出现一些兼容性问题。


  4. 代码合并困难,导致打包慢和流程低效。因为团队规模大,仓库也变得庞大,本地开发效率受到了影响。在进行全源码编译时,性能会比较低。为了解决这个问题,团队采用了拆分组件和仓库的解决方案。在抖音的场景下,虽然是一个整体的 APP,但背后有 400 多个独立的仓库。在多仓库的场景下,开发一个实际的功能可能需要修改多个仓库,如何进行代码合并和提取则成了问题。


  5. 缺乏完善的基础流程,导致新人加入后上手困难。各个部门在整个工程中会插入一些自己的工具和流程,对于新人来说,学习成本很高。因此,需要规范一套基础的流程,以便新人更快地上手需求开发。


  6. 缺少统一的管理平台,这在上千人合作的场景下尤为重要。虽然内部的代码仓库托管平台可以用于合并代码,但是在测试、上线和发布等方面,还需要一个统一的平台来管理。团队希望将所有的功能都集成到一个平台中,以便更好地管理代码。

整理流程

当时在 2017 年和 2018 年,我们做了一个简单的设想来应对具体的问题。我们对公司内部的研发流程进行了梳理,无论是哪一端,我们都是从本地进行开发。业务通过组件化的形式,通过多个仓库进行代码隔离,从而实现部门与部门之间的协作和功能与功能之间的集成。最终,通过合并代码到主仓库的主干分支上,并通过集成区对外发布。

我们的核心思想是先建立一个组件平台,通过组件平台解决前期开发过程中的耦合问题,从而让整个研发流程的入口更加清晰。第二步是规范合并代码的步骤,包括如何合并多个仓库的代码并进行测试,以及如何将代码合并到主干分支上。第三步是建立集成区,将合并的代码对应到相应的版本上,并对外发布。下图我们整体的路线图和预期的方式。


组件平台

首先,我们需要关注解决的问题是平台的组建,为此梳理出了四个主要目标。

  1. 项目组件化,我们将其分为三种场景:基础组件、业务组件和中台组件。基础组件包括字节内部的图片库和网络库等。业务组件是抖音内部的复杂功能,不放在主仓库中。中台组件包括支付、推荐和商城组件等。


  2. 规范通用库的维护流程。此举的重要性在于不同业务的组件会有各自的流程和版本号命名方式,我们希望将其通用化和规范化,以便整体的研发流程更好地适配组件。因此,我们定义了规范通用库的维护流程。


  3. 自动化的构建流程。以前,组件的打包依赖于 Jenkins,这存在稳定性和构建参数管理的问题。因此,我们需要实现自动化的构建流程。


  4. 实现基础库功能的自动化。当发布基础库时,通常会有一些后续的流程,但不同团队和业务有着各自的规范。我们希望能为所有业务提供标准的流程和规范,让大家在此基础上进行自定义开发,方便各自的功能被其他业务更好地使用。

架构

为了实现这个目标,我们设计了一个单的组件平台应用架构。我们希望在底层屏蔽 CocoaPods 和 Maven 这些工具的复杂性,使用户只需要关注两种使用场景:组件的维护和组件的使用。我们还将平台上的用户抽象为这两个角色,以便为平台的维护方和组件方提供不同的流程和功能支持。


能力

我们基于之前的设计,实现了以下组件平台的能力。

  • 二进制化。我们解决了本地全源码导致性能问题的方法,即通过平台支持二进制化来实现。这带来了一个问题,即如何确定主仓库的 V1 版本应该与哪些组件版本号匹配,以及哪些版本是稳定的?我们希望通过组件平台来解决这个问题。

  • 组件升级。例如,对于端监控组件等中台组件,我们可能会发布 V1 版本并进行一些 bug 修复,此时,我们希望能够通过独立的组件升级来发布版本 V1.2。这样,对于使用这个组件的业务来说,他们可以自行决定是否升级当前正在使用的组件版本号。

  • 组件订阅。通过关注组件,业务可以实时了解组件的升级状态,例如端监控组件。

  • 静态分析,可以检查代码的安全性和高危风险,例如代码中是否主动调用了获取用户位置的系统调用。由于每个国家的安全和风险策略都不同,因此需要通过代码的静态分析来找出相应规则。

  • 废弃功能。用户可以通过平台标注或废弃错误的版本,比如某版本发布时存在安全合规风险,需要强制废弃,这时我们可以打上废弃标签,以防止用户在线上使用错误版本。

  • 历史维护。我们还维护组件的历史升级信息,以方便回溯和追查问题。在组件平台上,可以查看当前组件被哪些 APP 使用,在哪些 APP 版本中使用,一旦组件存在问题,可以快速定位受影响的 APP 和版本,从而进行问题回溯。

  • 统计分析。统计组件的接入情况,以及各个组件的依赖与被依赖关系。

  • ISSUE 管理。用户可以通过平台入口,想关心的足迹提 ISSUE。

下图是我们实际落地的组件平台。其中有一个 APP 统计的功能,比如 DBWebImage 这个库是我们公司内部的通用 iOS 网络库,该库的作者现在也在字节,他对开源版本做了适配并放到了我们的内网,成为了字节内部所有 iOS 通用基础网络库。APP 统计功能可以展示该库目前被哪些字节 APP 使用过。另外,我们还有依赖组件的功能,但由于这里采用的是单仓多组件的概念,所以这个功能并未被使用,不会在组件平台上展示。这就是目前组件平台的标准概貌。右侧的信息栏会展示该组件被哪些 APP 使用,点击进去可以查看这些 APP 使用的具体版本。


研发流程
问题

我们在解决了组件平台的整体问题之后,就可以将公司内部所有客户端的研发流程定义成一个多仓的方式。但是,在实际操作中,我们遇到了以下一些问题。

  1. 业务高速发展导致各个团队的技术栈和研发流程存在较大的差异。


  2. 技术栈引入的研发流程之间也存在较大的差异。例如,对于安卓来说,会发 Snapshot,而对于 iOS 则不需要,这些差异都是由技术栈引起的。


  3. 成熟产品和高效迭代的产品研发流程也存在较大的差异。我们发现,对于一些对质量要求非常高的基础组件团队,他们更关注的是可控、可观察以及对质量的一些要求,而不是追求效率。然而,对于一些头部业务,例如抖音,他们需要在短时间内发布一个版本,并且有一定的效率要求。因此,不同的团队和业务线会带来一些流程设计上的差异。


  4. 国内和海外业务在研发流程上也有不同的要求。例如,面对国外的安全合规要求会引入较大的复杂度。由于各个国家的政策不同,海外业务可能需要在海外进行编译,而不是在国内进行编译,这会增加系统之间的配合复杂度。

目标

鉴于上述问题,我们思考了一种方案:是否能够通过一个或多个流程或方案,统一处理所有业务场景。


原子能力

经过我们对这个问题的梳理,我们将所有流程中最关键、最常用的功能整理如下。

1.安全合规。包含隐私检测,合规检查,静态检测等能⼒。

2.需求流转功能。MR 状态同步到需求任务系统,进⾏状态流转。这是各个平台都会有的一个功能。例如,在 RD 开发时,需求会绑定到一个 MR 上,当 MR 合入时,该需求就会自动流转成已完成状态。

3.Pipeline。自定义流水线支持业务自定义脚本任务执行。以前各个业务都是在使用 Jenkins 进行调度,但是在后面的过程中,机器维护成本很高,性能也会变得很低。因此,字节内部推出了一套云构建系统来代替 Jenkins。此外,对于 iOS 的打包编译,必须在 iOS 设备上进行,因此字节内部有 3000 台 Mini。

4.多仓合码。当一个需求需要在多个组件仓库中开发时,每个仓库提交的代码应该是原子性的,即必须要将相关的代码一起提交。如果某些代码没有被提交,其他同事再去合并代码时就无法保证整个代码仓库的原子性和隔离性,这可能会导致一些问题。

5.Code Review。支持多种配置和规则。在实际操作中,我们经历了三个阶段。第一个阶段是人拉人,也就是由我决定谁来审查我的代码。但实际操作中,有些研发人员无法保证他们选择的审查人员一定是准确的,甚至有些人会自己通过点“OK”就进入审查。目前我们处于第二个阶段,即规则拉人。虽然我不管你会不会拉人,但每个业务线都会有自己的审查规则,因此规则会比较多。例如,对于跨时区协作的同事,海外同事在维护文件或目录时,海外同事在相同目录下提出的代码审查请求会优先考虑由海外同事进行审查。我们在这个方面做了很多规则和配置。第三个阶段是智能拉人,即我来判断代码的风险大小,并决定由谁来审查代码,以及审查的质量如何。这是我们接下来要做的事情。

6.版本集成。对一个版本周期内的 MR 进行管理,管理不同版本之间 MR 的合入和发布。

7.组件发布。多仓⽀持⾃动的组件发版,并把版本号集成到主仓中。

8.多宿主。适用于 Flutter 场景的问题。例如,对于抖音来说,有些功能是使用 flutter 开发的。在开发完成后,我需要将此功能集成到抖音双端中,这是一个多宿主的场景。通常的操作是将其先集成到抖音的 iOS 中,然后再提交一个合并请求,将其集成到抖音的安卓中。对于这种场景,我们在类似于多仓的基础上实现了原子性保证。多仓可以理解为将多个仓库集成到一个主仓库或数组宿主仓库中。而多宿主则是将一个仓库集成到多个宿主中。这种方法对于中台团队来说是非常好的,例如,在支付中台的代码变更中,我可以将其集成到抖音、西瓜双端中,从而一次性完成集成。

9.冲突检查。检查 source 分⽀代码是否与 target 分⽀有冲突。这是我们需要关注的一个问题。在早期,字节一直依赖 GitLab,但由于规模太大,目前正在改造中。GitLab 在面对大量的数据时,其冲突检查是不准确的。因此,我们在这方面做了一些自建。

10.度量。在整个研发流程链路上埋点统计各个阶段耗时和成功率,帮助业务优化⾃⼰的流程。

整体架构

针对研发流程设计的产品,我们重点介绍应用架构设计的两个方面:流程引擎和 Pipeline 引擎。

  • 流程引擎是为了解决用户无法自行编排流程的问题,我们提供了一套流程引擎和原子能力,用户可以在其上编排流程,比如在安全合规完成后再执行 Pipeline 等。

  • Pipeline 引擎则是为了替代 Jenkins,我们自建了一套 Pipeline 引擎来解决这个问题。

另外,我们也重点建设了数仓,目的是为了衡量平台的质量和为业务提供的价值和效率,使整个研发流程的各个环节和链路都能被可观测。值得一提的是,数仓建设是我们 21 年和 22 年的重点项目之一,从无序开始经过两年的努力,我们已经将整个研发流程变得有序。现在我们的重点是衡量平台质量,以及为业务提供的价值和效率,确保整个环节和链路的可观测性。


流程引擎

下图展示了我们的流程引擎。其中有以下四个核心功能。

  1. 自定义编排。用户可以根据自己的需求自主编排研发流程的顺序和功能。流程引擎可以驱动上层的业务编排任务,并支持回滚、取消和跳过等能力。


  2. 可视化。前端会展示 MR 从创建到合入的整个过程,这样用户就可以清楚地了解当前合入阶段所处的位置。


  3. 调度。流程引擎驱动上层业务编排的任务,⽀持回 滚,取消,跳过等能力。


  4. 三方业务任务接入,支持将业务开发任务接入到自己的流程中,从而参与整个调度流程。


下图展示了流程引擎的设计,用户可以通过拖拽中间的节点来自定义研发流程的顺序和功能。类似于 OA 系统的编排思路,用户可以设定某一节点完成后应该执行的下一个节点。虽然 Pipeline 没有回滚的概念,但是流程引擎具备回滚功能,这一点在后续会有详细介绍。


下图展示了流程引擎在业务中的应用。流程引擎包含多个阶段,其中针对安卓场景,我们会拉出一个影子分支来解决某些任务需要将源代码分支和目标代码分支合并的问题。这个影子分支在流程结束之前不能被合并。流程的下一步是运行 CI Check,包括一些安全和合规的检查,以及 Pipeline。然后进行 Code Review 和 Feature Check,确保合入代码与需求相关且符合安全合规要求。针对 Lock 的问题,我们解决了多个仓库合并的原子性问题。最后,当组件通过验证并成功发版后,就可以继续进行后续的流程,并成功合入代码。


Pipeline

流程引擎与 Pipeline 的区别,可以回归到我们所编排的阶段。如上所述,Shadow branch、CI Check、Code Review 等都是我们所定义的阶段,流程引擎的主要作用就是控制这些阶段之间的回滚、前进和跳过等操作。而 Pipeline 的主要作用则是作为标准化的服务,执行我们所定义的一系列操作。


下面这张图展示了 Pipeline 的编排,这是由纯业务自己去实现的,可以实现关联关系和顺序执行等功能。


在整个流程建设完成之后,我们发现 Pipeline 是一个需要解决的重点问题,特别是对于业务来说,原有的 Jenkins 流程会经常出现问题,例如工区断网、Mac 机无法连接等问题,这些都会对业务产生影响。因此,我们需要将具体影响业务的点拿出来,一个一个去解决。首先,Pipeline 是一个需要重点解决的问题。我们的目标是在公司内部统一 Linux、Windows、macOS 等执行环境,比如 RTC 可能需要支持这三个环境。通过插件市场和插件试讲的形式,使得该作业更加通用。例如,如何将抖音的作业应用到头条中?同时,高度自定义,紧密结合研发流程,就是前面展示的那几点。下面这张图展示了整个应用架构中的 Pipeline 模块。其中,重点介绍下自建集群模块。我们目前有三种集群,分别是 Linux 集群、Windows 集群和 Mac 集群。


下面的图是一个用户可以使用的页面,用户可以在这个页面中编排自己的 Pipeline。例如,当抖音的发行包构建完成后,用户可以在这里设置静态检查任务,以确保代码没有问题,并上传构建包供 QA 测试。


下图展示的是我们 Pipeline 的原子市场,提供一些标准的官方 Job,比如包大小检测。字节内部、业务团队会有一些特别好的工具,检测完成后放到官方 Job 中,通过标准定义放进来,其他业务就可以使用了。例如测试团队的单元测试、静态检查、工具链团队为他们做的安卓编译和 iOS 编译。这样收敛后有一个好处,工具链团队做的分布式编译优化只需要在三方 Job 2 上集成插件,全公司就能使用了。


下图展示的是我们团队针对于 Pipeline 建立的 Mac Mini 机房。我们经历了三个阶段。在第一阶段,我们把迷你电脑放在自己的工位旁边,但由于我们经常搬家,每一次搬家都需要发公告通知云构建停机一天,这对于稳定性来说是不利的。另外,由于北美的研发不断增加,我们需要保证机器能够 24 小时不停地运行,而这在单一的机房里是不可能实现的。因此,我们建立了两个办公室、两个办公楼来满足需求。目前,我们已经将整个云构建迁移到标准的 IDC 中,用了 3000 台 Mac Mini。为了解决并发执行的问题,我们在 Mini 上建立了 Mac OS 的执行环境,并进行了虚拟化。



集成区

最后一步是将代码合并到对应的版本中,我们采用基于版本的理念,也就是基于版本驱动。APP 会以固定的时间周期进行发布,因此需要知道哪些需求需要与哪些版本一起发布。这个集成区用于管理这些问题,其中核心有四个功能。

  1. 版本日历,每个头部业务都会提前规划自己的版本,记录在版本日历中。


  2. 封版,例如对于抖音来说,每周四进行封版,即停止将当前开发分支合并进来,以当前最新的 commit 为节点,创建一个封板分支。


  3. 准入,控制当前版本中哪些 MR 可以被合并,对于重保版本,必须合并的 MR 不能漏掉,否则封版无法进行。


  4. 版本同步,面向 PMO,当需求开发完成后,需求状态会流转,这样 PMO 可以在项目管理软件中拉出当前版本的 MR、已开发的需求、已完成的需求和实际发布的需求,以及关联的版本等数据。



日历

下图展示了我们的版本日历,它规划了半年内每个版本的计划。这个日历的目的是让 QA、RD 和 PM 等多个角色能够协同工作。对于抖音而言,它有多个时间节点,比如每周都有版本计划,每个版本计划包括发版日期和封版日期等。通过这个日历,各个角色可以更好地进行协同工作。


准入

下图是准入页面,用于控制哪些 MR 没有申请 Ticket,这主要针对重保的一些功能。


状态流转

这里展示了一个状态流转的示例。一旦一个 MR 合并后,它就会与一个具体的需求关联起来,并将 MR 的所有状态同步到需求管理平台上。


例如,对于 iOS 开发来说,如果有一个安卓需求,那么在整个节点中,一旦有一个 MR 关联到该需求,该节点将自动向后流转并变为绿色。对于 PMO,他们会知道这个功能的 MR 已经被创建了,这意味着它已经完成开发,随后是安卓测试。一旦 QA 完成测试,该节点也会向后流转。因此,整个节点流转是全自动的。


以上就是我分享的内容,谢谢!

演讲人介绍

李腾飞,字节跳动后端研发工程师。目前主要工作重心在 Mobile DevOps 的平台服务开发和 Mobile 新研发流程的探索。2019 年加入字节,主要从事端相关的基础服务建设,参与过 Web 服务监控平台的开发。

今日好文推荐

Svelte使用心得:在个人项目中表现不错,但在大型企业项目中仍有待观察

推动UI层现代化:聊聊取代微前端的交互式微服务

前端全职岗位依然坚挺,广大同志不必惊慌

ECMAScript 2023:为JavaScript带来新的数组复制方法

文章引用微信公众号"前端之巅",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。