1. 先把比喻说清:缓存像冰箱,制品像快递
Cache:面向“重复构建”。同一套依赖(例如 lockfile 没变)时,你希望下次 workflow 直接拿到上次的热身成果,让构建少跑几段路。 但缓存不是数据库:它可能缺失、可能被淘汰、命中也不绝对。
Artifact:面向“阶段交接”。同一次 workflow run 内,你需要把 build/test/package 的结果给后面的 job(甚至人工排障时也要能追溯)。 制品不会跨 run 保证可用;它的价值在于这一次 run 的证据链完整。
2. actions/cache 深入:paths、key、restore-keys 的三角关系
2.1 paths:你要缓存什么目录
path 指定要打包/还原的目录。建议缓存“可重建但贵”的东西:依赖下载缓存、编译中间文件、构建工具本地仓库等。 如果你缓存的是“和构建强绑定”的产物(例如把整套 workspace 直接缓存),就更容易出现 ABI/版本漂移。
2.2 key:命中/失效的裁判
key 最重要。它决定这次能不能拿到上次“同版本依赖”的缓存。 常见做法是把 lockfile hash 放进 key:只要依赖变化,缓存必定失效并重新生成。
2.3 restore-keys:部分命中,换取更高速度
restore-keys 允许你在精确 key 没命中时,尝试更宽松的前缀。 这能把“完全 miss”的冷启动变成“半热身”。但前提是:你的目录内容对“轻微版本差异”仍然安全。
3. 常见生态的缓存模板(用关键目录而非整坨 workspace)
下面的片段是“可用模板”。你要做的不是照抄,而是把 path 与 key 对齐到你项目的依赖模型。
3.1 Node.js(npm / pnpm / yarn)
推荐缓存包下载与构建缓存,而不是把整个 `node_modules/` 作为目录直塞进去。
- name: Cache npm
uses: actions/cache@v4
with:
path: ~/.npm
key: ${ runner.os }}-node-${ hashFiles('**/package-lock.json') }}
restore-keys: |
${ runner.os }}-node-
3.2 Python(pip)
缓存 pip 的 wheel/download 目录,通常能显著降低依赖安装时间。
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${ runner.os }}-pip-${ hashFiles('**/requirements*.txt') }}
restore-keys: |
${ runner.os }}-pip-
3.3 Java(Maven / Gradle)
缓存本地仓库目录(如 Maven `~/.m2` 或 Gradle `~/.gradle`),让依赖解析更快。
4. Artifact:把构建证据“随包寄出”,让后续 job 可追溯
你应该把 artifact 看作“证据与成果的快递盒”。比如:测试报告(JUnit XML)、构建产物(zip / docker manifest 摘要 / wheel)、安全扫描结果等。 这样后续 job 不必重复构建同一个内容,或者至少能对齐“同一个版本的证据链”。
4.1 上传:actions/upload-artifact
- name: Upload test report
uses: actions/upload-artifact@v4
with:
name: test-report
path: test-results/
retention-days: 14
if-no-files-found: warn
4.2 下载:actions/download-artifact
- name: Download test report
uses: actions/download-artifact@v4
with:
name: test-report
path: ./test-results/
5. 缓存 vs artifact:别让“加速工具”变成“发布数据源”
6. 最小清单(把命中率和一致性一起做对)
- 先决定缓存目标:依赖/下载缓存/构建缓存,而不是整坨 workspace。
- 为缓存写清晰 key:把 lockfile hash 纳入 invalidation。
- restore-keys 只用于你确定“兼容”的前缀范围,避免脏命中。
- 跨 job 传递构建产物用 artifact,而不是指望缓存能“保活”。
- artifact 里不要塞 secrets;必要时用最小化字段或只上传报告。