CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
GitHub Actions CI/CD/课时/依赖缓存
RawBlame
课时目录12 节

GitHub Actions CI/CD

  1. 01CI/CD 与 GitHub Actions
  2. 02Workflow 文件结构
  3. 03Jobs 与运行器
  4. 04Steps 与 Actions
  5. 05Secrets 与环境
  6. 06依赖缓存
  7. 07构建 Spring Boot
  8. 08构建 Next.js platform
  9. 09构建推送镜像
  10. 10部署策略
  11. 11PR 检查与分支保护
  12. 12综合练习

依赖缓存

actions/cache、Maven .m2、npm node_modules。

25 分钟

依赖缓存

这一课你将能做到

  1. 说明为什么每次 CI 重下 Maven/npm 依赖又慢又费分钟数,以及缓存命中/未命中分别意味着什么
  2. 用 actions/setup-java 的 cache: maven 与 actions/setup-node 的 cache: npm 为本站 monorepo 两侧加速
  3. 读懂 hashFiles('…/pom.xml'|lock) 作 cache key 的失效逻辑,并知道哪些目录不该缓存

为什么要学

干净 runner 上没有你本机积攒的 ~/.m2 仓库或 npm 缓存。没有缓存时,每次 npm ci、每次 Maven 解析都像「第一次下载依赖宇宙」,日志刷屏、分钟数飙升。

缓存不是魔法:key 对了才命中;依赖变了应主动失效,否则会用到旧依赖,出现「本地更新了、CI 却像没更新」的诡异现象。

本课学会官方推荐的 setup-* 内置缓存,并理解底层 actions/cache 的 key 思维——后者在缓存任何非依赖目录(如构建工具链)时都要用。

先建立图像

第一次跑 CI
  下载依赖 → 按key写入缓存后端
  构建…

第二次跑(pom / lock 未变)
  同一 key 命中 → 恢复缓存 → 下载很少 → 构建更快

pom / package-lock 变了
  key 变了 → 未命中 → 重新下载 → 写入新 key

人话:缓存像快递柜格子。锁文件没变就开同一格取包裹;锁文件变了就换新格子,避免拿到过期货。

工具常见缓存什么推荐入口
Maven~/.m2/repositorysetup-java → cache: maven
npmnpm 全局缓存目录setup-node → cache: npm + lock 路径
通用任意 pathactions/cache@v4

跟着做

步骤 1:先看「无缓存」基线(可选但推荐)

临时用最简 Node job(无 cache)跑一次,记下 npm ci 耗时。有基线才知道后面省了多少——否则容易主观感觉「好像快了」。

步骤 2:为 platform 打开 npm 缓存

jobs:
  platform:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: platform
    steps:
      - uses: actions/checkout@v4

      - name: 安装 Node 并缓存 npm
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          # 让 setup-node 按 lockfile 管理缓存
          cache: npm
          cache-dependency-path: platform/package-lock.json

      - run: npm ci

第二次跑:Cache 相关 step 应出现 cache hit,npm ci 时间明显缩短。

步骤 3:为 backend 打开 Maven 缓存

jobs:
  backend:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: backend
    steps:
      - uses: actions/checkout@v4

      - name: 安装 JDK 并缓存 Maven 仓库
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: "21"
          cache: maven

      - run: |
          chmod +x mvnw
          ./mvnw -B verify

cache: maven 内部按 pom 哈希计算 key 并缓存 ~/.m2/repository。应看到依赖下载显著减少。

步骤 4:读懂手写 actions/cache(知其所以然)

      - name: 手写 Maven 缓存示例
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          # pom 变 → key 变 → 自动换新缓存
          key: maven-${{ runner.os }}-${{ hashFiles('backend/pom.xml') }}
          restore-keys: |
            maven-${{ runner.os }}-

三个字段的含义:

  • path:要缓存/恢复的目录
  • key:精确匹配才命中的身份证
  • restore-keys:前缀回退——精确 key 未命中时,尝试同前缀较旧缓存,仍可能加速大部分下载

日常优先用 setup-* 内置;手写用于特殊目录(如 Gradle、自定义工具链)。

步骤 5:故意让缓存失效

改 platform/package-lock.json(加一个依赖)后推送:应看到 miss 新 key,跑完生成新缓存。笔记写:「锁文件是缓存的身份证。」

步骤 6:列出「不要缓存」清单

  • 不要把 backend/target/、platform/.next/ 当依赖缓存——体积大、易脏、难保证正确性;跨 job 传递用 artifact
  • 不要缓存含密钥的目录或文件
  • 不要假设命中率 100%:配额、驱逐、分支作用域都会导致 miss,workflow 必须在 miss 时能完整安装

讲清楚要点

  1. 因为 runner 是干净的,所以缓存换的是「重复下载时间」,不是「跳过测试」——测试照样要跑。
  2. 因为依赖图由锁文件/pom 决定,所以 cache key 应包含它们的哈希——依赖变则自动失效。
  3. 因为 setup-node/setup-java 已封装常见路径,所以优先用内置 cache:——少踩 path 写错的坑。
  4. 因为 monorepo 有多个 lockfile,所以 cache-dependency-path 必须指到正确子路径——根目录乱指会错缓存或永远 miss。
  5. 因为缓存可能被驱逐(10GB 上限、7 天未用回收),所以 miss 时完整安装必须仍能成功——缓存是加速项,不是正确性来源。
  6. 因为构建产物易脏,所以 target/、.next/ 用 artifact 而非长期 cache——缓存面向可重算的输入,artifact 面向确定的结果。

你可能会懵的地方

  1. 「命中了为什么有时仍然有点慢?」 恢复缓存要下载解压,测试本身也要时间;加速的是依赖下载段,不是把 next build 变成零。

  2. 「缓存了还要 npm ci 吗?」 要。常见做法是缓存 npm 的缓存目录,npm ci 再据此快速安装并保证与 lock 一致。直接缓存整个 node_modules 更易脏(原生模块、平台差异),不建议入门用。

  3. 「restore-keys 会不会恢复到错误依赖?」 可能恢复「较旧但不精确」的缓存,再由包管理器补齐增量。精确 key 命中最理想,restore-keys 是兜底提速。

  4. 「PR 分支上生成的缓存,别的分支能用吗?」 缓存有分支作用域:默认分支创建的缓存所有分支可用;feature 分支创建的缓存只对该分支及其派生分支可见。所以「在 PR 里第一次跑永远 miss」可能是分支隔离所致,不是配置错。

  5. 「私有包注册表怎么办?」 缓存只存已下载内容,不替代认证;仍需 secret 登录 registry 后再安装。

错法对照

错法问题正解
每次 CI 无缓存重下全部依赖慢、费分钟setup-* 开 cache
key 写死成 maven-v1 永不变依赖升级 CI 仍用旧缓存key 含 hashFiles(pom/lock)
缓存整个 target/ 当加速脏产物、难复现需要跨 job 用 upload-artifact
cache-dependency-path 指到仓库根永远 miss 或错命中指 platform/package-lock.json
依赖缓存保正确性(删了 ci 只靠缓存)环境一变就崩miss 时必须能完整安装

卡点急救

  1. 缓存从不命中:按序查——key 是否包含 hashFiles() 且路径写对(monorepo 注意子目录前缀);是否在无缓存可见的分支作用域里;GitHub Actions 页 run 详情的 Post job 段(保存缓存在 job 结束后执行)是否报错。
  2. 缓存命中率突然归零:可能是旧缓存被驱逐(总上限 10GB、7 天未访问回收)或锁文件大幅变更换了 key。属预期行为,确认全量安装能成功即可。
  3. 「明明没改依赖,为什么这次慢了」:大概率是 miss 后全量重下(缓存被驱逐或首次在该分支跑)。偶发一次无碍;若频繁发生,检查 key 是否把易变内容(如 ${{ github.run_id }})误写进去了。

面试常问

  • CI 里依赖缓存的 key 应该怎么设计? 核心原则:key 反映「缓存内容的输入」。依赖缓存用包管理器锁文件(或 pom)的哈希做 key,如 maven-${{ runner.os }}-${{ hashFiles('backend/pom.xml') }},并配 restore-keys 前缀兜底;操作系统维度也要进 key,因为原生二进制跨平台不通用。避免把 run 编号这类每次必变的值写进 key——那等于永远不缓存。

  • 缓存会带来什么正确性风险?如何规避? 风险是「拿到过期缓存还以为依赖是新的」。规避:① key 绑定锁文件哈希,依赖变更自动失效;② 缓存只覆盖可重算的输入(依赖包),安装步骤照跑(如 npm ci)以对齐 lock;③ 把缓存当纯加速——miss 时流程必须完整可用;④ 构建产物不用缓存传递,用 artifact。

  • npm ci 和缓存怎么配合最稳? 缓存 npm 全局缓存目录(setup-node 的 cache: npm 自动做),安装仍用 npm ci——它严格按 package-lock.json 安装,缓存命中时只是下载更快,版本精确性不受影响。而缓存 node_modules 会让安装结果依赖上次环境,容易引入难以复现的漂移。

自测三问

  1. 为什么说缓存未命中时 workflow 仍必须能成功?
  2. setup-node 的 cache: npm 通常还要配什么字段?monorepo 里写什么值?
  3. 举两样不该当作依赖缓存的目录,正确的替代手段是什么?

小结一句话

用锁文件/pom 的哈希做 key,优先 setup- 内置缓存;缓存加速下载,不替代安装与测试。*

延伸

  • Caching dependencies to speed up workflows
  • actions/cache
  • 下一课:构建 Spring Boot——把这套骨架落到本站的 Maven + Spring Boot backend 上。
加载本节练习…
← 上一课Secrets 与环境下一课 →构建 Spring Boot