高速代码库如何做代码合并:主干开发与 CI 验证实践

在大型研发团队中,高速代码库的代码合并既要保证效率,也要保证主干分支稳定。本文以海外某大型互联网公司的工程实践为例,介绍其如何通过主干开发、持续集成(CI)、合入前验证、合入后验证和自动回滚机制,处理高频提交场景下的代码合并问题。

高速代码库如何做代码合并:主干开发与 CI 验证实践

海外某大型互联网公司的产品生态系统十分庞大,管理其基础设施并非易事。大约十年前,该公司从单一的单体代码库转向微服务架构,让不同团队能够独立管理各自的代码库。如今,数千名开发者在超过一万个活跃代码库中协作开发,其中一些代码库每天的提交次数甚至超过 300 次。

该公司采用主干开发模式。也就是说,某个代码库的开发者会将变更推送到主开发分支,例如 primary;他们会频繁执行变基操作,并尽量避免创建长期存在的特性分支。本文将重点介绍该公司的持续集成(CI)系统如何与不同规模的代码库协同工作,尤其是如何处理那些频繁向主干分支合入提交的代码库,从而确保代码既能及时合入,又能保持正确性。

对于希望借鉴这类实践的研发团队来说,代码合并效率并不只取决于 CI 系统本身,还与目标管理、需求评审、开发协作、测试验证、发布上线和知识沉淀等环节密切相关。PingCode 这类智能化研发管理工具,能够覆盖研发全生命周期管理,并打通研发过程中使用的多类工具,让研发数据在不同环节之间更顺畅地流转。

高速代码库合并的三大约束

在任何代码管理系统中,尤其是在采用主干开发模式时,都需要重点考虑两个与开发者效率相关的主要约束。

第一个约束是:主干分支应始终保持“绿色”状态。也就是说,在任何时候,如果将代码库检出到 HEAD,也就是最新版本,代码都应该能够成功编译、构建,并生成所有必要的制品。

当主干分支变为“红色”,也就是无法在 HEAD 上成功构建时,就需要回滚到上一个已知可用版本,或者由工程师提交新的变更来修复问题,使产品重新恢复可构建状态。后一种方式通常被称为“向前修复”。无论采用哪种方式,都可能耗费大量时间,并导致问题变更与修复变更之间加入的新功能或修复被延迟发布。

第二个约束是:开发者希望自己的变更能够及时合入代码库。将多个变更合入同一个代码库,最简单的方法是把所有提交的变更放入队列,然后逐个进行验证、测试,并依次合入代码库。

这种按时间顺序合入变更的最大问题在于,它无法扩展到高更新频率的代码库。假设某个产品的构建需要大约一小时,或者某个产品在接受变更前需要执行大量验证。如果多名工程师每天都向该代码库提交多个变更,那么变更队列就会不断增长,情况很快会变得难以控制。

另一种方法是将多个提交打包成批次进行处理。如果某个批次中有任一提交失败,就缩小批次规模并重新尝试。但在大规模场景下,这种方式同样会遇到问题。假设一个批次中有 20 个提交,只要其中一个提交存在问题,就可能导致大量不必要的重复运行。

除了前两个约束之外,由于该公司采用了基于微服务的架构,还存在第三个约束:系统必须同时适配高流量代码库和低流量代码库,并保持良好性能。重要的是,系统不能只针对高流量代码库进行优化,却牺牲低流量代码库中开发者的生产力。

为了解决这一问题,该公司允许开发者自定义流水线中需要运行哪些验证步骤,以及这些验证步骤的运行时机。下一节将对此进行说明。

在实际研发管理中,这类自定义策略往往也需要团队在任务、项目、文档、日程和审批等层面保持一致。对于更偏通用协作的团队,Worktile 这类项目协作系统可以承接任务分配、项目推进、文档沉淀、即时沟通、目标管理、日历、甘特图和工时等场景,帮助团队把代码合并之外的协作流程也管理起来。

合入前验证与合入后验证:CI 系统如何保障主干稳定

该公司同时使用合入前验证和合入后验证,以确保满足前面提到的约束条件。这也是许多基于主干开发的持续集成系统中的常见做法。

当开发者准备推送变更时,他们不会直接运行 git push,而是使用一个内部开发的 Git 子命令 git submit。这个命令行工具会启动一个验证任务,并立即将控制权返回给开发者。随后,该任务会运行合入前验证,并代表用户执行 git push 操作;如有必要,它还会执行变基操作。验证完成后,代码会被合入代码库,并通过服务端的合入后钩子启动合入后验证。

高速代码库如何做代码合并:主干开发与 CI 验证实践

值得注意的是,开发者的本地代码库不需要与远程 HEAD 保持同步,也可以成功调用这个命令行工具。这一点与直接执行 git push 不同。对高更新频率代码库的开发者来说,这曾经是一个巨大的痛点,也正是最初构建这一系统的重要原因之一。

过去,开发者经常陷入一个无休止的循环:运行测试,尝试推送变更,被提示 HEAD 已过期,执行变基操作,然后再次重复整个过程。而在 git submit 工作流中,合入前验证任务会替开发者完成推送操作,并在过程中执行变基,使变更与远程 HEAD 保持同步。

如果某个变更的合入前验证任务成功完成,那么在 git push 之后,系统会启动合入后验证。

高速代码库如何做代码合并:主干开发与 CI 验证实践

对于该公司大多数更新速度相对较低的产品而言,代码库每天大约只有 5 次提交。它们通常会在合入前测试中执行轻量级验证,例如代码检查、静态安全检查等,而将构建或编译步骤放到合入后测试中执行。

低更新频率代码库的工程团队通常更偏好这种快速的合入前验证方式。原因在于,他们认为变更及时上线带来的收益,要高于错误变更被合入所造成的影响。如果错误变更进入主干分支,合入后测试就会失败,开发者可以直接修复或回滚该变更。

此外,开发者还可以选择启用自动回滚机制。该机制会自动回滚任何导致主干分支变为红色状态的变更。这对于快速移除错误变更、恢复主干分支的绿色状态尤其有用。不过,对于稳定性较差的代码库来说,这也可能导致频繁回滚。

对于这些低更新频率代码库而言,错误变更进入主干分支所造成的影响,通常不如高更新频率代码库那么严重。因为在高更新频率代码库中,团队往往追求持续交付,每天可能发布多个版本。

对于迭代节奏较快的产品,系统会采用更严格的合入前测试,以降低错误变更进入代码库的风险。这是因为即使只有一个错误变更进入主干分支,也可能引发一系列问题,例如延迟发布,甚至影响本地开发效率。比如,开发者重新检出产品代码时,可能会遇到构建失败。

因此,并发量最高的产品通常会在合入前和合入后执行相同的验证。这会显著降低这些产品的流水线速度,但团队可以通过缓存等技术优化流程,从而缓解时间损耗。

如前所述,合入前验证主要用于防止错误变更进入代码库;合入后验证则用于捕获软冲突。所谓软冲突,是指两个提交各自都能成功构建,也不存在显式的合并冲突,但合并到一起之后却导致构建失败。

代码合并策略的取舍

该公司采用一种“先完成验证,先合入”的模式,但并不严格保留提交顺序。换句话说,系统并不采用严格的先进先出(FIFO)策略。

根据分析,先进先出无法满足该公司对可扩展性的要求。无论是简单地“一次处理一个提交”,还是前文提到的批量提交方式,都无法很好地适配高并发合入场景。

即使系统可以并发运行验证任务,但如果仍然只按提交顺序进行合入,开发者也可能遇到问题:许多提交可能会因为等待前面某个提交完成验证而被卡住。验证延迟可能由多种原因造成,例如 CPU 资源不足,或某个提交触发了额外测试。

本质上,在大规模持续集成系统中,并不存在完美方案。要同时优化速度和正确性,是一件极其困难的事。因此,和其他任何系统一样,该公司的方案也存在权衡。以下是其合并策略的主要优缺点:

优点缺点
每个代码库都具有较强的灵活性,可以自定义合入前验证和合入后验证。不保留严格提交顺序,也不采用先进先出(FIFO)原则。
主干分支几乎始终保持绿色状态。该公司更新频率最高的代码库,每年大约只有 5 次软冲突。更新速度较快的代码库需要运行两次验证,以确保错误代码不会进入并停留在主干分支中。
吞吐量高,因为提交不需要等待之前推送的提交完成。

市面上也有其他系统会利用机器学习等技术,预测某个提交或一组提交发生冲突的可能性。然而,该公司更看重工作流的灵活性和可组合性,希望工程团队能够选择最适合自身需求的方案。

换句话说,该公司不能采用“一刀切”的解决方案。它的工作流必须能够高效适用于各种规模的代码库,并且还要考虑单个代码库的工作流可能随时间变化。例如,参与某个代码库开发的工程师数量可能增加,也可能减少。

因此,该公司通过自身模型增强系统的可扩展性,同时允许各代码库根据自身需求,在代码正确性和合入及时性之间进行优化。

通过分享这种大规模代码合并处理方式的优缺点,本文希望帮助更多企业更深入、更有策略地思考:代码管理系统会如何长期影响开发者生产力。

文章包含AI辅助创作:高速代码库如何做代码合并:主干开发与 CI 验证实践,发布者:shang,转载请注明出处:https://worktile.com/kb/p/3979182

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
shang的头像shang

发表回复

登录后才能评论
注册PingCode 在线客服
站长微信
站长微信
电话联系

400-800-1024

工作日9:30-21:00在线

分享本页
返回顶部