尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Nexus代理阿里云仓库与Gradle发行版统一分发实践

Nexus代理阿里云仓库与Gradle发行版统一分发实践 先说一个我自己踩了很久才想明白的结论nexus代理阿里云仓库和gradle仓库这件事本质上不是为了省那几个G的带宽而是为了让团队里所有人的构建环境“收敛”到同一个可信源头。最近一个月内我已经处理了三起和依赖解析相关的报错一起是Could not resolve gradle:gradle:8.7一起是Could not install Gradle distribution from还有一起是Android Studio导入项目时一卡就是二十分钟。三个人的排查方式各不相同有手动改distributionUrl的有翻离线包的还有直接往~/.gradle里塞缓存的。我当时的判断是这不是大家的姿势不对是团队缺少一个统一的仓库入口。于是我把Nexus架起来上游全部指向阿里云仓库把Gradle插件仓库、发行版下载源也一并代理掉这套东西稳定运行之后这些零零散散的问题基本绝迹了。如果你也是Java、Android或者Gradle插件开发为主想在公司内网搭一套靠谱的依赖中转这篇内容正好可以当作业。下面不绕弯子直接按我这个月的实操顺序拆开讲。1. 先搞清楚一件事为什么是Nexus代理阿里云而不是全员直连镜像1.1 单独配镜像的问题缓存是“各配各的”国内开发者使用阿里云仓库大概是最常见的加速手段把build.gradle里的repositories一改maven { url https://maven.aliyun.com/repository/public }表面上看构建速度确实上来了。但一个多人团队里这种做法的代价非常隐性每个开发者的~/.gradle/caches是独立的同一个依赖十个人就要从阿里云下载十遍。Jenkins和本机构建各走各的源一旦某个版本被清理掉就会出现“我本地能编CI挂了”的诡异局面。如果你想统一替换为另一个镜像源就得等全组人手动改配置还要担心有人漏改。1.2 Nexus在这套架构里扮演的三种角色Nexus Repository Manager 3在Gradle体系里可以同时承担三个角色很多人只用了其中一两个Proxy Repository作为阿里云仓库、Google Maven、Gradle Plugin Portal的代理内部缓存下载过的组件团队里有人拉过一次后面所有人都走本地缓存。Group Repository把多个Proxy仓库聚合到一个地址下Gradle端只配一个URL顺序由Nexus控制这是“统一入口”的关键。Hosted Repository放团队私有的构件、SNAPSHOT包、Gradle发行版压缩包相当于一个自建的私服坐标。1.3 收益看得见的三件事跑通之后团队内至少有三件事能说得清外网依赖出问题的概率被隔离在Nexus一台机器上开发者本地不再需要频繁访问海外仓库如果你在内网办公还能让构建过程不依赖个人网络。构建缓存是可共享的Nexus会把代理下来的jar、pom、gradle插件存在Blob Store里第二次解析直接命中。Gradle发行版也能统一分发新同事入职配环境的时候不需要再对着services.gradle.org的下载地址干等设置好distributionUrl指向内网即可。2. Nexus仓库三件套的配置顺序Blob Store、Proxy、Group2.1 第一步先规划Blob Store别上来就建仓库Nexus 3安装好之后默认的所有仓库都挂在default这个Blob Store上。如果你只是临时跑一下问题不大但如果你打算长期承载团队依赖我的建议是先规划单独的Blob Store尤其给Gradle发行版这类体积大、类型杂的文件单独开一个。Blob Store说白了就是Nexus在磁盘上存数据的目录不同Blob之间互相隔离。好处有两个一是将来你删某个仓库时对应数据能整块清理不会把default撑爆二是Gradle发行版的zip动辄一两百MB如果和几万个jar混在一起后期做备份和维护都会很痛苦。我一般会建三个Blob Storemaven-blob存Maven组件和Gradle插件dist-blob存Gradle发行版zip等二进制分发物dev-blob存团队自研快照和release注意Blob Store一旦创建是不能改挂载目录的所以创建时一定要选对路径这一步不值钱但改起来很麻烦。2.2 创建代理阿里云Maven的Proxy仓库URL别填错在Nexus管理界面左侧菜单点Repository Repositories右上角Create repository选择maven2 (proxy)。这里最容易翻车的是把URL填成页面地址比如填了https://maven.aliyun.com/这是错的代理仓库的Remote Storage必须精确指向具体的repository路径。我建议按下面这组URL来建仓库名称格式Remote Storage地址用途aliyun-central-proxymaven2https://maven.aliyun.com/repository/central阿里云代理的Maven Centralaliyun-google-proxymaven2https://maven.aliyun.com/repository/googleAndroid/Google Maven依赖aliyun-gradle-plugin-proxymaven2https://maven.aliyun.com/repository/gradle-pluginGradle插件中心镜像aliyun-public-proxymaven2https://maven.aliyun.com/repository/public阿里云聚合源老项目兼容用central-official-proxymaven2https://repo1.maven.org/maven2/官方源兜底防止镜像缺版本google-official-proxymaven2https://dl.google.com/dl/android/maven2/Google官方源兜底gradle-plugin-portal-proxymaven2https://plugins.gradle.org/m2/插件门户兜底关键点是阿里云镜像并不保证每个坐标都全我遇到过某个冷门库的.pom在镜像上返回404但官方源明明存在。所以不要只配阿里云要用“阿里云优先官方源兜底”的组策略。提示创建Proxy仓库时Version policy建议选ReleaseStorage里把Blob Store选到第二步规划的maven-blobNegative Cache里的Cache for not found components建议设小一点比如30分钟否则阿里云某个404会被Nexus记很久等官方源有了也拉不到。2.3 Group仓库怎么聚合顺序为什么重要代理仓库建好之后还不能直接用因为Gradle端不应该关心你是从阿里云拿的还是从官方源拿的它只需要一个稳定URL。这时需要创建一个maven2 (group)仓库。我创建的maven-public组里按以下顺序排列aliyun-central-proxyaliyun-google-proxyaliyun-gradle-plugin-proxycentral-official-proxygoogle-official-proxygradle-plugin-portal-proxy顺序的原则是命中率高的放在前面兜底源放最后。因为Nexus在group仓库里是逐个向后问的前面的仓库404了才会问后面的。阿里云源绝大多数情况下能命中放前面效率最高官方源只是备胎放在最后。2.4 给Gradle插件和Google源也建对应的代理仓库Android项目里的依赖不仅有普通jar还有classpath和plugins两套体系。比如你在settings.gradle里写pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } }Gradle会去这三个源里找插件。如果你只代理了Maven Central插件照样可能卡死。所以上面那张表里的aliyun-gradle-plugin-proxy和aliyun-google-proxy一定要建并且在Group里把它们一起聚合进去。我实测过用https://maven.aliyun.com/repository/gradle-plugin代理Gradle Plugin Portal上的插件绝大多数插件都能拉通只有很少量的插件坐标在镜像上缺失会回源到后面的gradle-plugin-portal-proxy。3. Gradle侧接入三种写法的适用场景3.1 单项目写法与settings.gradle里最容易踩的坑最直观的引入方式就是修改项目的settings.gradle把仓库全部替换成内网Nexus组地址pluginManagement { repositories { maven { url uri(http://nexus.example.com:8081/repository/maven-public/) } maven { url uri(http://nexus.example.com:8081/repository/gradle-plugin-portal-proxy/) } } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url uri(http://nexus.example.com:8081/repository/maven-public/) } } }这里最容易踩的坑有两个。第一个是插件仓库和依赖仓库不要混在一个group里以为万事大吉Gradle对pluginManagement的解析逻辑比较严格如果插件在组内靠后的仓库里才命中解析时间会明显变长。第二个是dependencyResolutionManagement模式下如果你在各自的build.gradle里还写repositoriesGradle直接报错repository is ignored这个报错本质上不是Nexus的问题但很多人第一反应是去查Nexus。3.2 init.gradle全局配置“每次新建项目配一遍”的解法热搜词里有一条非常真实“每次新建安卓项目都要配置gradle的镜像源”。这个问题的标准解法不是每次改build.gradle而是写一份init.gradle放到GRADLE_USER_HOME/init.d/目录下。我目前的做法是在Jenkins那台构建机上放一份在开发者本地的~/.gradle/init.d/也放一份内容简化如下allprojects { buildscript { repositories { maven { url http://nexus.example.com:8081/repository/maven-public/ } } } repositories { maven { url http://nexus.example.com:8081/repository/maven-public/ } } } settingsEvaluated { settings - settings.pluginManagement.repositories { maven { url http://nexus.example.com:8081/repository/maven-public/ } maven { url http://nexus.example.com:8081/repository/gradle-plugin-portal-proxy/ } } }这样做的效果是任何项目只要Gradle版本一致拿过来构建时仓库自动走Nexus。开发者不再需要关心自己有没有改过repositoriesCI也不需要为每个项目临时注入仓库脚本。注意init.gradle里直接写allprojects会影响到所有项目如果你公司同时用Gradle和Android Gradle Plugin建议把init.gradle拆成不同目录下的脚本比如在init.d/下建maven-repo.gradle和android-repo.gradle分环境加载。3.3 关键细节repositories顺序与metadata缓存Gradle解析Maven依赖时会先请求maven-metadata.xml再根据里面列出的版本去拼jar路径。如果Nexus代理的阿里云仓库缓存了旧的maven-metadata.xml即使阿里云后面更新了依赖版本你的构建也拿不到因为Gradle不会重新查metadata。这个问题的表现是新版本发布了但构建始终显示Could not find artifact xxx:1.2.0或者一直用旧版本。排查时先看Nexus的Caching配置我一般这样处理Metadata max age设成-1也就是每次请求都重新从上游校验metadata。对SNAPSHOT版本仓库必须关闭Metadata cache否则快照永远指向旧值。如果确认某个版本已经发布但组内404去Nexus里手动执行Expire cache再让开发者重试。4. 深挖两个“看似能代理、实际要踩坑”的场景4.1 Gradle发行版distribution下载慢能不能代理很多团队卡在“Android Studio导入项目太慢”其实大头不是依赖而是Gradle Wrapper要从services.gradle.org/distributions/下载一个一百多MB的gradle-x.x-bin.zip。Nexus本身是支持raw (proxy)类型仓库的可以代理普通静态文件目录所以把Gradle发行版也纳入Nexus完全可行。我建了一个gradle-dist-proxyRemote Storage填https://services.gradle.org/distributions/然后开发者的gradle-wrapper.properties改成distributionUrlhttp://nexus.example.com:8081/repository/gradle-dist-proxy/gradle-8.7-bin.zip注意这里有个坑services.gradle.org是支持按版本重定向的Nexus的raw proxy对重定向的跟随偶有失败如果你发现某个版本的zip下载到一半报错不要硬扛直接在Nexus里建一个gradle-dist-hostedhosted仓库把对应zip手动上传再把distributionUrl指向hosted仓库。如果你不想自己下载也可以用腾讯的Gradle发行版镜像https://mirrors.cloud.tencent.com/gradle/作为source路径结构一致开箱即用。4.2 SNAPSHOT版本缓存导致构建永远拿到旧包团队内自研依赖一般用SNAPSHOT版本比如com.example:core:1.0-SNAPSHOT。Nexus对SNAPSHOT的处理逻辑和release完全不同它缓存的是带时间戳的快照文件如果上游仓库的SNAPSHOT更新了但Nexus里Cache expired还没到构建就会一直用旧快照。这个问题最容易出现在“开发者本地改了依赖、push到私有仓库然后CI拉取依赖还是旧包”的场景。我的做法是为私有hosted仓库单独建一个GroupSNAPSHOT相关仓库放最前。把SNAPSHOT仓库的Metadata max age设为0。团队规范发布SNAPSHOT后至少在CI里加一步gradle --refresh-dependencies确保Gradle绕过本地和Nexus缓存强制刷新。4.3 阿里云镜像缺版本回源策略怎么设计前面说了用官方源兜底但回源策略不是简单把官方源排后面就完了。Nexus的Group在查找时一旦某个Proxy仓库返回了404它会记入Negative Cache这个缓存时间如果太长会导致官方源兜底形同虚设。所以我在实践中把central-official-proxy、google-official-proxy、gradle-plugin-portal-proxy三个兜底仓库的Negative Cache设成15分钟而阿里云的几个代理仓库可以设成60分钟或更长。这样一个冷门依赖在阿里云上404后最多15分钟就能从官方源回源成功而热门依赖在阿里云上命中后后续直接走Nexus缓存速度很快。另外再给一个经验如果你自己发布私有组件一定不要把maven-public这个Group直接用于发布。正确做法是建一个独立的private-releaseshosted仓库上传地址走这个库下载地址才是Group。否则一旦Group里暴露hosted写入权限IOException和权限问题会让你欲哭无泪。5. 排错实录热词里的几个高频报错5.1 Could not resolve gradle:gradle:8.7先检查坐标再检查仓库热搜里有Caused by: org.gradle.internal.resolve.ModuleVersionResolveException: could not resolve gradle:gradle:8.7这个报错我见过太多次了。它本身的含义是构建脚本里声明了gradle:gradle:8.7这个依赖但无论在Nexus还是官方源都找不到该坐标。注意Gradle发行版和Gradle API坐标是两回事gradle:gradle这个坐标并不存在于Maven Central只有gradle-api、gradle-core这类内部模块。如果你的脚本里真的写了gradle:gradle:8.7正确的做法是改用dev.gradleplugins或直接从本机Gradle安装目录提供的jar引入。排除完坐标问题后再去Nexus的Search里查gradle-api:8.7是否已经缓存。如果Nexus里没有而阿里云上也不存在那就别折腾Nexus了换官方源或者调整Gradle版本。5.2 Could not install Gradle distribution fromdistributionUrl的排查链路Could not install Gradle distribution from这个报错的排查链路很固定打开项目gradle/wrapper/gradle-wrapper.properties看distributionUrl指向哪里。如果是services.gradle.org直接在浏览器试一下该URL能不能下载能下载说明网络没问题问题在本地代理或Nexus中转。如果走的是Nexus的gradle-dist-proxy确认Nexus这台机器能不能正常访问上游很多内网环境只有Nexus机器本身有外网权限其他机器全走内网。确认~/.gradle/wrapper/dists里是不是残留了半个下载文件有时候Gradle不会自动修复损坏的zip删掉对应目录重试一次就行。如果Nexus返回的是HTML页面而不是zip基本是raw proxy的路径拼错了检查distributionUrl路径中是否少了gradle-8.7-bin.zip。提示手动分发离线包的正确方式是直接下载zip传到Nexus hosted仓库然后distributionUrl写成.../repository/gradle-dist-hosted/gradle-8.7-bin.zip。这条我验证过多次离线安装Gradle 6.8/6.9.4/8.7都适用。5.3 Nexus管理密码找回与匿名访问设置Nexus装好后默认管理员账号是admin初始密码在sonatype-work/nexus3/admin.password这个文件里如果忘了以后登录不了先看这个文件是否还能找到。文件没了也别慌可以到sonatype-work/nexus3/db/里用Nexus自带的重置机制最笨的办法是备份配置后直接改nexus.properties加一行nexus.security.initialPassword.bypasstrue重启它会重新生成初始密码。当然生产环境这样做要谨慎如果这台机器还在提供服务尽量别在高峰期重启。匿名访问方面我的建议是内网且没有敏感组件时开匿名读权限让开发者不用配账号密码就能下载依赖如果公司有安全审计要求就建一个deployer账号用于上传reader账号仅用于下载并在Nexus的Capabilities里关闭匿名访问。5.4 顺带提醒搜索时别把Nexus仓库和桌面Dock混为一谈热词里大量出现winstep nexus、nexus桌面美化、nexus系统天地、nexus插件安装教程之类的内容那是Windows桌面Dock工具Winstep Nexus跟仓库管理Nexus完全不是一个东西。我起初排错时也差点在社区里搜到一堆无关结果。我的建议是检索仓库相关问题时用nexus sonatype、nexus repository manager、nexus proxy aliyun这样的关键词能省掉很多噪音。最后这套方案实际运行后的体验我这边把Nexus和阿里云仓库、Gradle发行版代理全部跑通之后最大的感受是团队的构建问题从“每个人各显神通”变成了“统一问Nexus”。新同事入职装好JDK配一下GRADLE_USER_HOME/init.d再也不用问“这个依赖怎么下载这么慢”“为什么我本地编译报Could not resolve”。CI上如果出现解析失败第一件事也是在Nexus上查缓存和上游状态几步之内就能定位到是阿里云缺版本还是metadata缓存过期还是distributionUrl配错。如果你打算照着搭我再给一条个人建议先小范围跑Android和Java两个典型项目确认maven-public组能命中、插件能解析、Gradle发行版能下载再推广到全团队。整个过程中最不值得省的就是Blob Store的规划和Negative Cache的调参这两步做好了后面维护会很省心。
返回列表