好邻居:如何减少 Gradle 构建对 Maven Central 的流量

随着 Maven Central 不断增长的负载,本文将解释如何确保您的 Gradle 项目不会成为问题的一部分。

目录

引言

Maven Central 是 Java、Kotlin 和其他 JVM 社区的重要资源。它提供了最流行的开源库、开发工具、文档和其他构件的仓库。该服务不仅被开发人员大量使用,也被 CI/CD 流水线和自动化工具大量使用。

虽然使用 Maven Central 的缓存代理是最佳实践,但并非所有人都这样做。这不仅会导致过高的流量成本和构建延迟,还会使您的构建和流水线由于保护 Maven Central 的限流机制而面临更多的减速和失败。

正如 Sonatype 首席技术官 Brian Fox 在 2024 年 6 月的这篇博客文章中写道:“Maven Central 总带宽的 83% 被仅占 1% 的 IP 地址消耗。此外,其中许多 IP 来自一些世界上最大的公司……我们将开始与我们的提供商合作,实施针对极端重度消费者的限流机制,这些消费者实际上正在滥用社区资源。”随着这项工作的进展,更多的组织将面临限流,包括那些使用 Gradle 的组织,特别是如果他们严重依赖 Maven Central 而没有启用缓存代理的话。

虽然 Gradle 目前没有内置 Maven 的 mirrorOf 功能来将所有依赖解析路由到缓存代理,但组织可以部署一个具有相同效果且与现有 Gradle 版本兼容的配置。这得益于 Gradle 的灵活性和丰富的编程模型。

ℹ️ Gradle 插件门户呢?
在这篇博文中,我们展示的配置更改也影响了 Gradle 插件门户解析插件依赖方面的使用。这也是 Gradle Inc. 免费提供的社区资源,就像 Maven Central 一样,它需要管理其工作负载和带宽使用。请注意,插件门户还对构件解析使用速率限制,以应对日益增长的负载。

使用内部构件仓库 #

对于希望管理构件并降低网络流量成本的组织来说,拥有一个 Maven Central 的缓存 Maven 仓库代理是标准做法。不同的供应商或开源组织提供了此类仓库的多种实现,其中大多数都与 Maven Central 兼容。

配置 Gradle 项目以使用组织仓库而非 Maven Central #

在 Gradle 中,您必须显式定义一个或多个仓库来解析依赖。为此,请在您的 settings 脚本中声明适当的仓库

// in settings.gradle.kts
pluginManagement {
    repositories {
        // Replace Gradle's default for plugin resolution by internal repository
        maven {         
            url = uri("https://company/com/proxy-repository")
        }
    }
}

plugins {
    // Declare settings plugins here
}

dependencyResolutionManagement {
    // Make sure projects do not add other repositories by accident
    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
    // Define the internal repository as the only repository to use
    repositories {
        maven {         
            url = uri("https://company/com/proxy-repository")
        }
    }
}

// Other settings content

在上面的示例中,将 URL 替换为您的组织的仓库位置。此片段是一种定义内部项目仓库的自定方式,它利用了集中式仓库声明功能。使用上述配置的项目将不会向 Maven Central 发送请求,尽管组织仓库本身可能需要这样做来缓存资源。

此声明与 Gradle 6.8 及更高版本兼容。有关仓库声明语法和要求的更多信息,请参阅 Gradle 文档

覆盖 Maven Central 和插件门户仓库的 URL #

修改构建文件并非总是可行。例如,在您自己的基础设施中构建开源项目时,您可能希望全局覆盖构建中定义的仓库配置。

这是一个 init 脚本,它将替换所有使用 Maven Central 和 Gradle 插件门户的项目的 URL

// init.gradle.kts
apply<InternalRepositoryPlugin>()

class InternalRepositoryPlugin : Plugin<Gradle> {
    override fun apply(gradle: Gradle) {
        val canBeMirrored: Spec<MavenArtifactRepository> =
            Spec { r -> r.getName().equals(ArtifactRepositoryContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME)
                    || r.getName().equals("Gradle Central Plugin Repository") }

        val useMirror: Action<MavenArtifactRepository> = Action {
            val mirrorUrl: String = "https://company/com/proxy-repository"
            setUrl(mirrorUrl)
        }
        val configureMirror: Action<RepositoryHandler> = Action {
            withType(MavenArtifactRepository::class.java)
                .matching(canBeMirrored)
                .configureEach(useMirror)
        }
        gradle.beforeSettings(Action {
            configureMirror.execute(getBuildscript().getRepositories())
            configureMirror.execute(getPluginManagement().getRepositories())
        })
        // Remove the settingsEvaluated part if you are using Gradle <6.8
        gradle.settingsEvaluated(Action {
            configureMirror.execute(getDependencyResolutionManagement().getRepositories())
        })
        gradle.beforeProject(Action {
            configureMirror.execute(getBuildscript().getRepositories())
        })
        gradle.afterProject(Action {
            configureMirror.execute(getRepositories())
        })
    }
}

在上面的示例中,将 mirrorUrl 替换为您的内部仓库的 URL。部署此 init 脚本有多种选项;请参阅 Gradle 文档了解选项。

上述 init 脚本完全兼容 Gradle 6.8 及更高版本。对于 Gradle 6.0 到 6.7(包括),需要删除 gradle.settingsEvaluated 部分。不支持更低的版本。

支持更简单的镜像 #

上面的示例演示了 Gradle 的灵活性和编程模型如何解决构建挑战,即使没有开箱即用的功能。代码并不简单,因为 Gradle 区分了插件和项目仓库,并且有集中和本地的方式来声明这两种仓库。

诚然,解决镜像用例可以更容易。目前,Gradle 不支持定义仓库镜像,类似于 Apache Maven 对镜像的支持。我们正在关注 gradle/gradle #27808 中的一个开放功能请求。首先,我们计划改进关于如何从构建外部注入仓库配置的文档。我们还将根据社区对本文和即将到来的文档更新的反应,评估是否需要一个核心功能。

故障排除 #

要解决依赖解析和缓存问题,您可以使用我们免费的 Build Scan 服务。Build Scan 分析构建的有效依赖以及构件解析和下载的性能。

每个依赖项都指示它是从哪个 URL 解析的

Build Scan dependency origin

您可以获取构建中所有网络流量的全局视图

Build Scan networking view

要了解有关启用 Build Scan 的更多信息,请参阅 Gradle 文档

总结 #

减少流向 Maven Central 的流量不仅仅是为了提高构建性能;它还关乎构建弹性、高效和可持续的开发流水线。无论您是从事开源项目还是管理企业级构建,使用构件仓库都可以显著减少冗余下载,最大限度地降低限流风险,并节省网络成本。

虽然并非每个项目都能采用所有解决方案,但即使是小的改进也能产生有意义的影响。随着公共基础设施(如 Maven Central)面临的压力越来越大,社区遵循最佳实践并减少不必要的负载变得越来越重要。

如果您尚未这样做,请考虑审查您组织的当前依赖解析设置,如果您受到 Gradle 中缺乏仓库镜像的影响,请在 Gradle issue #27808 中表达您的意见。

另请参阅 #

讨论