Jenkins 缓存陈旧速查:增量构建命中旧产物、cleanWs 策略与 Maven 本地仓库脏
更新时间:2026-10-24。FAQ 速查页,面向「代码明明改了、构建出的包还是旧的」「本地删了依赖就编译不过,CI 上却一直绿」的同学。Jenkins 缓存陈旧的本质是:为了提速而保留的复用层——工作区、包管理器本地仓库、编译器增量缓存、Docker 层缓存——保存的内容与当前源码的真实状态脱节,构建在「旧地基」上跑出了看似正常的结果。缓存是 CI 提速的必要手段,问题在于缓存的失效策略往往没人配:默认全量保留,旧产物、旧依赖、旧镜像层越积越多,直到某天命中一份与代码不匹配的缓存,故障以「灵异现象」的形式出现。病因收敛在六根轴上:增量构建命中旧产物、cleanWs 清理时机不对(清了不该清的或没清该清的)、Maven 本地仓库残留旧快照、node_modules 与 lock 文件版本漂移、Docker 层缓存复用旧基础层、共享缓存目录被并发构建交叉污染。排查的核心动作只有一个:清缓存重跑一次,对比结果。如果缓存没问题但构建偶发失败,转 Jenkins 构建偶发失败速查。
本文要回答的问题
- 改了源码,构建产物里的类文件时间戳为什么还是上周的?
- cleanWs 放在流水线哪一步才不误删?
- Maven 本地仓库里的旧 SNAPSHOT 怎么定位与清理?
- node_modules 缓存与 package-lock.json 不一致会发生什么?
- 共享挂载的缓存目录在多节点下为什么会互相污染?
一、典型现象与触发线索
缓存陈旧的现场证据藏在构建日志的时间戳与依赖解析输出里(控制器地址以 jenkins.example.com 占位,路径以 /proj/demo 占位):
# 现场一:改了代码产物却没变——增量缓存命中
+ mvn package -o
[INFO] Nothing to compile - all classes are up to date
[INFO] Building jar: /proj/demo/target/app-1.0.jar
# 现场二:本地仓库解析到旧快照
Downloaded from snapshots: com/demo/core/2.3-SNAPSHOT/core-2.3-20261018.090212-4.jar
# 而最新提交应该是 -5
# 现场三:cleanWs 清理时机错误,构建中断
+ ./gradlew assemble
> Task :compileJava FAILED
error: file not found: /proj/demo/build/generated/Proto.java读法:缓存问题的共性是「结果与代码状态脱节」——编译器说没东西可编、包管理器解析出旧版本、清理步骤删掉了别人刚生成的文件。第一步永远是带干净工作区重跑一次:干净构建过了而增量构建挂,病因就锁定在缓存层。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| Nothing to compile 但代码变了 | 增量缓存命中旧产物 | 看 target/build 目录时间戳 | 清构建目录或强制全量重编 |
| 依赖解析到旧 SNAPSHOT | Maven 本地仓库残留 | 查本地仓库里该构件的版本目录 | 删旧版本目录或加 -U 强制更新 |
| cleanWs 后构建缺生成文件 | 清理步骤放错位置 | 看 cleanWs 在 stage 里的顺序 | 清理放构建前或后置阶段 |
| 前端构建用错依赖版本 | node_modules 与 lock 漂移 | 对比 node_modules 实际版本与 lock | 用 npm ci 按 lock 全新安装 |
| 镜像构建复用旧基础层 | Docker 层缓存命中 | 看构建日志 CACHED 标记 | 关键层加 --no-cache 或改缓存键 |
| 换节点后构建行为不一致 | 共享缓存目录交叉污染 | 对比不同节点的缓存内容 | 缓存按节点隔离或加锁 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是现象入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓判据:「干净工作区重跑一次」是唯一分水岭——干净构建正常而增量构建异常,问题必在缓存层,接下来就按「工作区、包管理器仓库、镜像层」三层逐一清。两类都挂则不是缓存问题,回到代码或环境方向排查,别在缓存里空耗。
四、逐条排查展开
增量构建命中旧产物是最常见的假象。 Maven、Gradle、make 都靠时间戳判断「没变化就跳过」,而 CI 工作区里的文件时间戳来自检出时刻——git checkout 切换分支后,未改动的文件保持旧时间戳,与旧产物一比对就「一切最新」。症状是 Nothing to compile 但产物里装的是旧代码。处置:涉及切换分支或检出点大幅回退的构建,先清 target/build 目录;对可靠性要求高的发布构建直接全量编译,别省这几分钟。
cleanWs 的时机错了比不清更糟。 把 cleanWs() 放在流水线收尾而中间有 stage 依赖生成的文件(如 protobuf 生成代码、前端构建产物),下一个 stage 一跑就报文件不存在;放在每次构建开头又可能清掉上个构建特意保留的缓存。处置:清理动作放构建最前(保证起点干净)或最后(收尾释放磁盘),按「这个工作区下一次被谁用」决定;声明式流水线里用 post { always { cleanWs() } } 表达收尾语义最直观。
Maven 本地仓库的旧 SNAPSHOT 要定期清。 SNAPSHOT 版本按设计就是会变的,本地仓库里缓存的旧时间戳版本(如 core-2.3-20261018-4.jar)在离线模式或更新策略为 daily 时会被继续解析。处置:构建加 -U 强制更新快照;更严重的情况直接删本地仓库里该构件的目录让它重下。共享节点的本地仓库建议按周清理,或挂载到可按需清空的独立目录。
node_modules 缓存必须与 lock 文件联动。 把 node_modules 整个目录缓存下来提速是常见做法,但 lock 文件变了而缓存没失效,装出来的依赖树就是旧的。处置:缓存键带上 lock 文件哈希(如 package-lock.json 的 md5),内容变了缓存自动失效;安装用 npm ci 而非 npm install——前者严格按 lock 全新安装,不改写 lock,行为可复现。
Docker 层缓存的失效要靠内容哈希而不是信任默认。 镜像构建日志里的 CACHED 标记意味着该层复用了旧结果,COPY package*.json ./ 之后的 RUN npm install 层在依赖没变时复用是特性,但基础镜像更新后旧层继续被复用就是坑。处置:关键阶段(拉基础镜像、装系统包)定期 --no-cache 重建;多阶段构建里把易变内容放在靠后的层,让缓存命中率最大化且失效面最小。
共享缓存目录在多节点并发下会交叉污染。 多个节点挂载同一个 NFS 缓存目录,两个并发构建同时写 Maven 本地仓库,可能写出半个 jar 或互相覆盖元数据。处置:缓存按节点隔离(每节点本地盘),必须共享时对写操作加锁;不可变内容(已发布的 release 构件)共享读是安全的,可变内容(SNAPSHOT、工作区)绝不共享写。
五、规范做法与预防
- 缓存键绑定内容哈希:凡是缓存,键里必带输入内容(lock 文件、源码树哈希),内容变缓存自动失效。
- 发布构建默认全量:日常开发构建用增量提速,发布与打 tag 的构建强制干净工作区全量编译。
- 清理策略写成文档:cleanWs 的位置、本地仓库清理周期、镜像缓存策略写进流水线模板注释。
- 缓存目录可独立清空:本地仓库、构建缓存挂独立卷,出问题时一条命令清空不波及其它数据。
- 定期巡检缓存命中率与体积:缓存体积无节制增长或命中率异常都是失效策略出问题的信号。
- 可复现安装优先:前端用
npm ci、Python 用锁定文件全新安装,把「装出什么样的依赖树」变成确定事件。
这一篇在知识体系里的位置
本篇是 Jenkins 排错速查簇的「缓存与复用」入口:缓存干净但构建仍然时好时坏,看 Jenkins 构建偶发失败速查;构建因缓存膨胀把磁盘打满,看 Jenkins 内存句柄耗尽速查;依赖装不上属于工具链问题看 Jenkins 构建工具未安装速查;工作区被并发构建互踩看 Jenkins 重复触发构建速查。「干净重跑做分水岭、缓存键绑定内容哈希」这两条对所有 CI 系统的缓存治理通用。
一句话总结
Jenkins 缓存陈旧先用「干净工作区重跑一次」定界:干净正常而增量异常,问题就在缓存层——增量编译靠时间戳被骗、Maven 本地仓库残留旧 SNAPSHOT(加 -U 或删目录)、node_modules 与 lock 漂移(换 npm ci)、Docker 层缓存复用旧层(关键层 no-cache)、共享缓存并发污染(按节点隔离)。根治靠缓存键绑定内容哈希、发布构建强制全量、清理策略写进模板,让缓存提速而不藏污。