Chapter 06

缓存策略与 Artifacts 管理

通过智能缓存将构建时间从分钟压缩到秒级,用 Artifacts 在 Jobs 间传递构建产物

缓存(Cache)原理

为什么需要缓存?

每次 GitHub Actions Workflow 运行时,都是在全新的虚拟机上执行。这意味着每次都要重新下载 npm 包、pip 包、Maven 依赖等——这可能占据整个构建时间的 60-80%。通过缓存,可以将依赖下载从每次 2-5 分钟压缩到秒级。

缓存的工作流程

理解 actions/cache 的内部机制,有助于设计更好的缓存策略:

缓存查找(Restore)
在步骤执行前,GitHub 根据 key 精确匹配缓存。精确命中时直接恢复;未命中时按 restore-keys 中的前缀依次查找最近的匹配缓存(降级恢复)。降级恢复可以复用大部分文件,只需增量下载新依赖。
缓存保存(Save)
Job 结束时(成功情况下),actions/cache 会将 path 中的文件压缩并上传到 GitHub 缓存存储(每个仓库 10GB 限制)。如果 key 与已有缓存完全相同,则不会重复保存(避免重复占用配额)。
缓存范围
缓存默认仅在当前分支和默认分支(main/master)之间共享。Pull Request 可以读取 base 分支的缓存,但其新建的缓存对 base 不可见。这防止了 PR 对主分支缓存的污染。
缓存过期
缓存在 7 天内未被访问会自动删除。超过 10GB 上限时,GitHub 按 LRU 策略删除最旧的缓存。可在仓库 Actions → Caches 页面手动管理缓存。

Cache Key 设计原则

Cache Key 是决定是否命中缓存的关键。好的 Key 设计要在缓存命中率缓存新鲜度之间取得平衡:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    # 完美 Key:OS + 锁文件哈希
    # 锁文件变化 = 依赖变化 = 缓存失效(重新下载)
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    # restore-keys:降级匹配,即使不完全命中也能部分复用
    restore-keys: |
      ${{ runner.os }}-npm-
      ${{ runner.os }}-

不同语言的缓存配置

# Node.js(npm)
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: ${{ runner.os }}-npm-

# Node.js(yarn)
- uses: actions/cache@v4
  with:
    path: ~/.yarn/cache
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}

# Python(pip)
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}

# Rust(Cargo)
- uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/registry
      ~/.cargo/git
      target/
    key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

# Go
- uses: actions/cache@v4
  with:
    path: |
      ~/go/pkg/mod
      ~/.cache/go-build
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}

# Maven(Java)
- uses: actions/cache@v4
  with:
    path: ~/.m2/repository
    key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
使用 setup-* Actions 的内置缓存

许多官方 setup Action(setup-node、setup-python、setup-go 等)都内置了缓存支持,只需添加 cache: 'npm' 参数即可,比手动配置 actions/cache 更简单。推荐优先使用内置缓存。

Artifacts 管理

Cache vs Artifact 的区别

Cache(缓存)
用于跨 Workflow 运行持久化中间产物(如依赖包)以加速构建。跨运行复用,保留时间较长(默认 7 天未使用后删除),大小限制 10GB。
Artifact(产物)
用于在同一 Workflow 运行内的不同 Job 之间传递文件,或供用户下载(如编译好的二进制、测试报告)。默认保留 90 天,大小限制 500MB(每次上传)。

在 Jobs 间传递构建产物

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

      # 上传构建产物
      - uses: actions/upload-artifact@v4
        with:
          name: dist-files          # 产物名称
          path: dist/               # 上传的路径
          retention-days: 5         # 保留天数(1-90)
          compression-level: 9      # 压缩级别(0-9)
          if-no-files-found: error  # warn/error/ignore

  test-e2e:
    needs: build
    runs-on: ubuntu-latest
    steps:
      # 下载上一个 Job 的构建产物
      - uses: actions/download-artifact@v4
        with:
          name: dist-files
          path: ./dist              # 下载到指定目录

      - run: npx serve dist &
      - run: npx playwright test

  release:
    needs: [build, test-e2e]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist-files

      - name: 创建 Release 并上传产物
        uses: softprops/action-gh-release@v2
        with:
          files: dist/*.tar.gz

上传测试报告

- name: 运行测试(即使失败也继续)
  run: npm test -- --reporter=junit
  continue-on-error: true

- name: 上传测试报告
  uses: actions/upload-artifact@v4
  if: always()    # 无论成功失败都上传
  with:
    name: test-reports-${{ matrix.node }}
    path: |
      test-results/
      coverage/
    retention-days: 30

缓存优化实战:Node.js 项目完整示例

# 完整的 Node.js 项目缓存优化配置
name: CI with Cache

on: [push, pull_request]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 方式1:使用 setup-node 的内置缓存(推荐,更简洁)
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'                # 自动缓存 ~/.npm,key 基于 package-lock.json

      # 方式2:手动配置 actions/cache(更灵活,可控制更多参数)
      # - uses: actions/cache@v4
      #   with:
      #     path: ~/.npm
      #     key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
      #     restore-keys: ${{ runner.os }}-npm-

      # ci 安装(比 install 更严格,保证 lock 文件一致性)
      - run: npm ci

      # 缓存 Next.js 构建缓存(下次构建只重新编译修改的部分)
      - uses: actions/cache@v4
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.{js,jsx,ts,tsx}') }}
          restore-keys: |
            ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
            ${{ runner.os }}-nextjs-

      - run: npm run build
      - run: npm test

      # 上传覆盖率报告
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: coverage-report
          path: coverage/
          retention-days: 14

多平台构建产物收集

# 并行构建多平台,收集所有产物发布
name: Multi-platform Release

jobs:
  build:
    # 矩阵构建:同时在三个平台构建
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build:desktop

      # 每个平台上传各自的产物(名称包含 OS 信息)
      - uses: actions/upload-artifact@v4
        with:
          name: app-${{ matrix.os }}        # ubuntu-latest, windows-latest, macos-latest
          path: |
            dist/*.exe                       # Windows
            dist/*.dmg                       # macOS
            dist/*.AppImage                  # Linux
          if-no-files-found: ignore          # 跨平台文件不存在时忽略而非报错

  # 收集所有平台的产物并发布
  release:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: write                       # 创建 Release 需要写权限
    steps:
      # 下载所有平台的产物(不指定 name 则下载全部)
      - uses: actions/download-artifact@v4
        with:
          path: artifacts/                  # 下载到子目录,按 artifact 名自动分目录

      - name: 列出所有产物
        run: ls -la artifacts/

      # 创建 GitHub Release 并上传所有文件
      - uses: softprops/action-gh-release@v2
        with:
          files: artifacts/**/*
缓存安全注意事项

缓存中不应存放敏感信息(密钥、凭证)。虽然缓存对仓库成员可见,但不会暴露给公开用户。Fork PR 的工作流程可以恢复父仓库的缓存(只读),但无法创建或覆盖父仓库的缓存,这防止了缓存投毒攻击。

本章小结

本章核心要点