远程和分布式构建模式
Gradle 构建工具的一个经常被请求的功能是能够执行远程或分布式构建。但这到底意味着什么?请求背后的动机是什么?这篇文章将探讨远程构建与分布式构建之间的区别及其变体。由于这些概念没有行业范围内的统一术语,因此本文的目的是概述这些模式以及它们之间的关系。
除了两个特定于 JVM 的引用之外,这些观察结果通常适用于使用任何语言或生态系统的软件项目。
但为什么呢?
这些功能通常在缩短本地开发人员机器上的构建时间的情况下进行讨论。构建周转时间延长会阻碍本地和 CI 环境中的生产力,但本地构建体验对开发人员的情绪有不成比例的影响。
术语
术语“远程”和“分布式”构建在行业中并不总是使用一致,并且经常互换使用。下面,我们将为每个术语给出不同的定义。首先,我们还将定义更基本的“构建缓存”优化。
什么是远程构建缓存?
我们将讨论的第一个包含远程组件的模式是构建缓存。类似于 增量构建,构建缓存避免执行编译源文件或执行测试等 CPU 密集型操作。增量构建将最近一次本地操作的输出保留在磁盘上,而构建缓存则通过存储和重用操作的任何先前执行结果来实现这一点 - 就像从备份中恢复文件一样。更重要的是,缓存可以是仅限本地的,也可以在工程师之间共享(远程构建缓存)。CI 环境通常配置为写入云中的共享缓存。然后,每个工程师的机器从共享缓存中提取结果。这意味着相同的源代码只需要在 CI 主机上构建一次,从而避免了在开发人员机器上进行昂贵的本地编译调用。
简而言之,构建缓存将中间构建工件存储在远程服务器上以加快构建速度。与远程和分布式构建不同,没有任务实际在远程执行。
Gradle 构建工具 构建缓存 功能自 2017 年推出以来,在减少本地和 CI 构建时间方面非常成功。
远程构建的变体
一般来说,“远程构建”是指通过将整个过程委托给另一台计算机来减少构建时间的方法。通常,远程计算机在计算资源和内存方面比本地计算机更强大。它可以在统一的、精心策划的环境中或隔离的环境中托管构建,不受其他本地进程的资源争用影响。
在实践中,远程构建可以采用以下四种形式之一。
传统解决方案:远程桌面/屏幕共享
最原始的远程构建模式是使用久负盛名的 VNC 或 RDP 协议进行远程桌面。虽然这些工具确实是低技术含量的屏幕共享工具,并且会受到低网络带宽和高延迟的严重影响,但它们确实允许在远程机器上构建软件。我们只出于完整性考虑提及这种历史解决方案,因为现代远程 IDE 提供了更具响应性的解决方案。
什么是远程构建?
在远程构建场景中,构建命令在本地机器上调用,但实际计算发生在远程机器上。源文件和其他支持构建的输入最初存在于本地机器上,并与远程机器同步。同样,在构建结束时,生成的构建输出/工件将从远程同步回本地机器。
这开辟了一些有趣的可能性,但也并非没有挑战。一方面,远程构建可以添加在不同硬件架构或操作系统上构建代码的能力(例如,Windows 客户端在 Linux 主机上构建)。而远程主机的硬件可能会导致构建速度显着提高。同时,保持源文件、项目依赖项、构建工件和构建软件的短暂、中间状态的同步开销可能会很快超过原始速度优势。
Gradle 目前没有提供自己的远程构建解决方案,但存在一些有趣的补充开源解决方案
- Mainframer:“一个在远程机器上执行命令并在文件之间同步的工具。”
- Mirakle:“一个 Gradle 插件,允许您将构建过程从本地机器移动到远程机器。”
什么是远程 IDE?
远程 IDE 类似于远程构建场景,但有两个主要区别。首先,IDE(而不是命令行或构建工具调用)处理与远程主机的通信。其次,源代码可以专门在远程主机上克隆。在这种模式下,IDE 在本地机器上充当“瘦客户端”。IDE 代码的“后端”部分作为后台进程在远程主机上运行。这种方法的好处是项目源代码不需要存在于本地,并且远程构建的同步开销减轻了。与本地开发一样,IDE 的“后端”和构建过程之间存在资源争用的可能性。
如今存在三个优秀的远程 IDE 示例,它们都与 Gradle 构建工具无缝协作
- Visual Studio Code:通过其“Remote - SSH” 扩展提供远程 IDE 体验。
- IntelliJ IDEA:JetBrains 客户端和网关协同工作,在本地运行瘦 IDE,同时远程构建。
- 舰队:虽然处于封闭预览阶段,但 JetBrains Fleet 提供了类似于 Visual Studio Code 的轻量级远程 IDE 体验。
像上面例子中提到的远程 IDE 可以提供非常愉快的开发体验。如果远程主机或虚拟机与 VCS 和二进制工件存储系统位于同一个数据中心,克隆代码和解析外部依赖项的速度会非常快。如果做得好的话,远程 IDE 比本地构建有很大的改进。
云开发环境
将远程 IDE 的概念更进一步,云开发环境旨在自动化远程主机的配置,重点在于一致性和协作。云开发环境通常是集中管理的,确保所有工程师都拥有可靠、统一的环境,无需在本地机器上构建。一些云开发环境结合了其他开发工具,例如 IDE、错误/问题跟踪和源代码控制。结合远程 IDE,将工程师可能需要的全部工具包装到一个单一的、精心策划的环境中,可以带来非常愉快和高效的开发体验。
与远程构建/IDE 一样,额外的性能体现在更快的 CPU 内核和通过每台机器的额外内核实现的改进的并行性,相对于本地环境而言。虽然使用云开发环境并不直接与构建工具的选择相关,但 Gradle 构建工具可以在任何远程环境中透明地工作。
三个突出的云开发环境示例是
集中管理的云开发环境可以提供多种优势
- 更快的启动时间:工程师无需手动签出代码并设置本地机器:环境可以配置为“开箱即用”。
- 更快的反馈时间:假设远程机器具有更高的性能,并且与其他关键资源(如二进制工件存储)位于同一个位置。
- 多平台支持:例如,从 macOS 笔记本电脑远程构建 Windows 环境,反之亦然。
- 统一的环境:本地工程师机器上出现不一致的风险更低。
- 安全性和审计:这指的是数据中心中的集中管理环境,在该环境中可能需要保护知识产权或可能需要符合合规性要求。
远程总结
查看上面提供的可用解决方案,我们看到最令人兴奋的创新发生在远程 IDE 和云开发环境领域。远程构建也有一些有趣的方面,但对本地开发人员体验的边际收益可能被增加的复杂性所抵消。因此,我们鼓励绕过远程构建,转而使用远程 IDE,同时关注云开发环境的新兴功能。
分布式构建的变体
与在单个远程机器上执行所有工作的远程构建不同,分布式构建专注于将工作分解成小块,并在多台机器之间分配。远程执行器通常从池中分配,类似于 CI 分配的工作方式,尽管每个分布式工作项的执行时间相对较短。
分布式构建作为构建的或多或少透明的功能实现,因此开发人员可以像触发本地构建一样在本地触发它们。执行工作项所需的输入会传输到执行器,生成的输出会同步回来。
不要忘记分布式构建的后台基础设施需求。如果无法获得足够的远程构建代理,构建实际上可能会变慢。管理复杂的构建分发代理池会增加额外的维护,例如监控/可观察性、扩展和故障转移/容错。
在我们深入了解真正的分布式构建解决方案之前,我们将首先描述在多台机器上分配构建工作的最基本技术。
手动优化:CI 扇出
CI 扇出是一种通过将构建(通常是测试子集)拆分为多个 CI 作业来减少端到端构建时间的技术,以便在不同的代理上执行工作。虽然它比没有并行或单机并行有所改进,但它也存在重大缺点。CI 作业的划分必须手动配置,并且特定于每个 CI 平台。虽然这减少了 CI 上的整体构建时间,但它不会使本地构建受益。这种方法的其他挑战在 这里 描述。
现代测试分发
根据我们的经验,运行测试而不是编译源代码通常是构建速度慢的主要原因,尤其是在 JVM 生态系统中。JVM 上的测试执行天生适合分布式,因为测试通常在单独的短暂 VM 中执行,该 VM 的系统环境、类路径和内存使用量已指定。这些参数很容易传达给远程主机以进行分布式执行。
在以分布式方式运行测试时,与在本地分区或并行化测试执行时相同的担忧适用。良好的测试方法应该是原子的,只依赖于测试夹具中明确的环境设置/拆卸指令。一个精心设计的、非原子的测试依赖于另一个测试的副作用,在以分布式方式执行时可能会以意想不到的方式失败。
Gradle Enterprise 的 测试分发 商业功能在远程主机池上执行测试,其并行性比本地实现的更高。它还在同一主机上执行测试类中的所有方法,从而缓解了上面提到的非原子测试失败的最常见原因。
一般分发
顾名思义,通用分发是一种在远程主机上执行任何构建操作的方式。必须仔细考虑环境变量或其他系统属性,因为与在本地构建相同的代码相比,这些属性可能会对分布式构建结果产生意想不到的更改。此外,哪些构建操作值得分发的开销,这个问题很难回答。
以下工具采用通用方法进行构建分发
- Pants: https://www.pantsbuild.org/docs/remote-execution
- Bazel: https://bazel.build/docs/remote-execution
在决定通用分发解决方案之前,请注意可能需要进行重大权衡。通用分发构建环境可能会为构建逻辑增加重大复杂性。“拆分包”编译(有时称为1:1:1 规则)是一种将源代码分成更小的编译单元以帮助分发的技术,但会给已经很复杂的依赖管理问题带来更多痛苦。有关更多详细信息,请参阅构建文件的粒度。
分发非测试工作(如编译)的好处取决于所用编程语言的编译速度。例如,与“原生”语言相比,Java 编译速度相对较快。使用上面提到的“拆分包”编译,可能会实现少量性能改进。但是,对于相对较小的性能提升,维护复杂构建逻辑的额外痛苦可能不值得。考虑到 Gradle Build Tool 的增量编译器为 Java 提供了除 javac 之外的重大性能提升,这一点尤其如此。
有关通用构建分发的权衡的更多详细信息,请参阅通用构建分发:游戏规则改变者还是噱头?文章。
远程和分布式构建的共同因素
在远程和分布式范式中,都会产生大量的网络流量。将源代码序列化到远程主机或在代理之间同步构建工件可能会产生巨大的开销。本地客户端与远程主机之间,或分发代理与工件存储之间的网络距离可能是主要因素。网络连接应具有高带宽和低延迟,以防止削弱通过远程和/或分布式工作实现的理论收益。
此外,管理远程主机或分布式代理池会产生更多成本和开销。将需要额外的工程投资来提供标准化的环境。应谨慎行事,以避免通过响应峰值使用周期和停机时间来造成资源饥饿或过度分配。
总结
在这篇文章中,我们回顾了利用远程机器的构建模式,阐明了远程和分布式构建的定义,并讨论了它们的变体。
我们首先解释了远程构建缓存,它是利用远程机器进行构建优化的最基本方法。然后,我们详细阐述了远程构建模式,并指出了远程 IDE 和云开发环境领域正在发生的令人兴奋的创新。最后,我们解释了 CI 扇出技术以及分布式构建中的不同模式,包括测试分布和通用分布。
反馈
如果您对我们的 论坛 或 Gradle 社区 Slack 有任何问题,请告诉我们。