GitHub Actions 缓存策略:加速 CI/CD 流水线 5 倍
npm install 3 分 15 秒。
这是我去年接手的一个项目的 CI 构建时间。每次推送代码,我都得盯着 GitHub Actions 的日志转圈,等那个绿色的勾勾出现。说实话,那段时间我经常切到其他窗口摸鱼——反正要等。
后来加了缓存,同样的一次构建,40 秒搞定。快了差不多 5 倍。
这不是魔法,就是配置对了 GitHub Actions 的缓存策略。今天这篇文章,我会把踩过的坑、测试过的数据、以及可以直接复制使用的配置模板都整理出来。如果你也在等 CI 构建,这篇可能能帮你省下不少咖啡时间。
一、缓存机制核心概念
先搞清楚缓存是怎么运作的,不然配置的时候容易踩坑。
GitHub Actions 的缓存机制其实挺简单——就三步:查找 → 恢复 → 保存。你定义一个 key,GitHub 会在所有缓存里找有没有匹配的。找到了,直接恢复到你的工作目录;没找到,等任务跑完再存一份新的。
但有几个硬限制你得知道:
| 限制项 | 数值 |
|---|---|
| 单仓库缓存上限 | 10 GB |
| 单个缓存文件上限 | 5 GB(实际超过 1GB 就容易出问题) |
| 缓存保留期限 | 7 天未被访问即删除 |
| 全局并发上传限制 | 最多 5 个缓存同时上传 |
我见过有人踩过 10GB 的坑——项目依赖太多,缓存越攒越大,最后新缓存存不进去,旧的又被清掉了,每次构建都是”冷启动”。
还有一点容易混淆:Cache 和 Artifact 不是一回事。Cache 是给 CI 用的,追求快;Artifact 是给人看的,比如构建产物、测试报告,要长期保存。Cache 有 10GB 限制,Artifact 没有上限(但会占你的仓库存储)。
另外还有 Docker Layer Cache,这是专门给 Docker 构建用的,逻辑跟普通缓存不太一样,后面会单独讲。
二、缓存键设计策略
缓存能不能命中,全看 key 设计得对不对。这是整个缓存策略的核心。
hashFiles() 是什么
GitHub 提供了一个内置函数 hashFiles(),它能算出文件的哈希值。常用在 package-lock.json 或 yarn.lock 上——依赖不变,哈希就不变,缓存就能命中。
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
这段的意思是:生成一个形如 npm-Linux-a1b2c3d4e5f6... 的键。只要 package-lock.json 没变,这个键就不会变。
restore-keys:备用方案
但依赖总会更新,这时候就需要 restore-keys。它是个”降级匹配”机制:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-{{ runner.os }}-
优先匹配完整 key。匹配不到?降级找 npm-Linux- 开头的旧缓存。虽然没有完全命中,但至少 node_modules 里大部分包都有了,只需要增量安装新依赖。
三种键命名模式对比
我测试下来,推荐这三种模式:
简单模式(适合小项目):
key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
版本模式(适合多 Node 版本):
key: {{ runner.os }}-node{{ matrix.node-version }}-{{ hashFiles('**/package-lock.json') }}
多路径模式(适合 monorepo):
key: {{ runner.os }}-{{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
如何判断缓存是否命中
actions/cache 会输出一个 cache-hit 变量:
- uses: actions/cache@v4
id: cache-npm
with:
path: ~/.npm
key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
- name: Check cache hit
run: echo "Cache hit - {{ steps.cache-npm.outputs.cache-hit }}"
true 表示精确命中,false 表示部分命中或完全 miss。你可以根据这个变量决定要不要跑 npm ci:
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
三、实战配置示例
理论讲完了,直接上代码。以下配置我都实测过,可以直接复制使用。
npm 缓存(推荐用 setup-node)
其实 setup-node 已经内置了缓存功能,比手动用 actions/cache 更简洁:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # 或 'yarn'、'pnpm'
一行搞定。但如果你想缓存其他目录(比如 node_modules),还是得用 actions/cache:
- uses: actions/cache@v4
with:
path: node_modules
key: {{ runner.os }}-nm-{{ hashFiles('**/package-lock.json') }}
restore-keys: {{ runner.os }}-nm-
我的建议:优先用 setup-node 的内置缓存,除非你有特殊需求。
yarn 和 pnpm
yarn 的缓存目录跟 npm 不一样:
- uses: actions/cache@v4
with:
path: |
~/.yarn/cache
~/.yarn/install-state.gz
key: yarn-{{ runner.os }}-{{ hashFiles('**/yarn.lock') }}
pnpm 更特殊,它用的是全局 store:
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-{{ runner.os }}-{{ hashFiles('**/pnpm-lock.yaml') }}
Python/pip 缓存
Python 项目的缓存路径:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-{{ runner.os }}-{{ hashFiles('**/requirements.txt') }}
restore-keys: pip-{{ runner.os }}-
Docker Layer Cache
Docker 构建最耗时。好消息是 BuildKit 支持 GitHub Actions 的缓存后端:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
type=gha 表示用 GitHub Actions 的缓存服务存 Docker 层。实测下来,一个 5 分钟的镜像构建能降到 1 分钟左右。
Go 模块缓存
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-{{ runner.os }}-{{ hashFiles('**/go.sum') }}
Rust Cargo 缓存
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-{{ runner.os }}-{{ hashFiles('**/Cargo.lock') }}
Rust 编译慢,缓存能省大量时间。但要小心 target 目录会越来越大——建议定期清理。
四、性能优化与最佳实践
我整理了一些实测数据和踩过的坑,希望能帮你少走弯路。
性能基准数据
根据 RunsOn 的测试报告(2026 年 1 月更新),合理配置缓存后:
| 操作 | 无缓存 | 有缓存 | 提升 |
|---|---|---|---|
| npm install | 3 分钟 | 40 秒 | 约 5 倍 |
| yarn install | 2 分钟 30 秒 | 35 秒 | 约 4 倍 |
| Docker build | 5 分钟 | 1 分钟 | 约 5 倍 |
| pip install | 45 秒 | 8 秒 | 约 5 倍 |
缓存命中率在 70-90% 之间,取决于你的键策略设计得够不够好。
常见陷阱
不要直接缓存 node_modules
我一开始就是这么干的,结果踩了大坑。
# 不要这样写
path: node_modules
node_modules 是平台特定的——Linux 上装的包,Windows 跑起来可能有问题。正确做法是缓存全局缓存目录(~/.npm),让 npm ci 自己去组装。
跨 OS 缓存要用 GNU tar + zstd
默认的 tar 在 macOS 和 Windows 上格式不一样,会导致缓存恢复失败。加这个配置:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
enableCrossOsArchive: true
缓存污染问题
有时候缓存里存了有问题的依赖,导致构建一直失败。解决办法:
- 手动删除缓存:进入 GitHub 仓库的 Actions → Caches 页面,点击删除
- 强制更新 key:给 key 加个前缀或时间戳,让它重新生成
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
最佳实践清单
最后总结几个要点,配置前对照检查:
- 优先使用官方 action 的内置缓存(setup-node、setup-python)
- key 要包含 hashFiles,不然依赖更新了缓存还在用旧版本
- restore-keys 写上,降级匹配能保命
- 不要缓存 node_modules,缓存全局目录
- 定期清理过期缓存,避免超出 10GB 限制
五、常见问题解答
Q1: 为什么我的缓存命中率不高?
最常见的原因是 key 变得太频繁。比如你在 key 里加了时间戳或者分支名,每次 push 都会生成新 key。解决方案:只用 runner.os 和 hashFiles,去掉不必要的变量。
另一个原因是 hashFiles 匹配了不该匹配的文件。比如你写了 hashFiles('**/*.json'),结果配置文件改一下,缓存就失效了。改成只匹配 package-lock.json 或 yarn.lock。
Q2: 缓存空间超限怎么办?
10GB 看起来挺大,但 monorepo 或 Docker 缓存很容易爆。解决方案:
- 定期清理:GitHub Actions → Caches,手动删除旧的
- 分开缓存:不同依赖用不同 key,避免一个缓存存所有东西
- 用 self-hosted runners:它们没有 10GB 限制
Q3: self-hosted runners 需要特殊配置吗?
不需要特殊配置,缓存机制一样用。但 self-hosted runners 有个优势:缓存是本地存的,不存在网络传输延迟,恢复速度更快。缺点是缓存不会自动清理,得自己写脚本定时清。
Q4: 如何强制更新缓存?
改 key。加个前缀版本号:
key: npm-v3-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
或者直接删掉旧缓存,让系统重新生成。
总结
说了这么多,其实就是一句话:用好缓存,CI 能快 5 倍。
我给你算笔账——假设每次构建省 2 分钟,每天跑 10 次,一个月就是 600 分钟,差不多 10 小时。这时间够写好几篇文章了。
如果你刚上手 GitHub Actions,建议先从 setup-node 的内置缓存开始,一行配置就够用。等遇到瓶颈了,再回来研究更复杂的键策略和 Docker Layer Cache。
对了,这篇文章是 GitHub Actions 实战指南系列的第 3 篇。之前写过 CI 流水线搭建和部署策略,感兴趣可以翻翻历史文章。
下次推送代码的时候,记得看看你的构建时间。能不能从 3 分钟降到 40 秒,试试就知道。
配置 GitHub Actions 缓存加速 CI/CD
通过配置 GitHub Actions 缓存,将 npm install 构建时间从 3 分钟降至 40 秒
⏱️ 预计耗时: 10 分钟
- 1
步骤1: 选择缓存方案
根据项目包管理器选择缓存方案:
• npm 项目:优先使用 setup-node 内置缓存
• yarn/pnpm 项目:配置缓存路径
• Docker 构建:使用 BuildKit 的 gha 后端 - 2
步骤2: 设计缓存键
使用 hashFiles() 基于锁文件生成稳定键:
• 基础模式:{{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
• 添加 restore-keys 作为备用匹配
• 避免在 key 中加入时间戳或分支名 - 3
步骤3: 添加缓存配置
在 workflow 文件中添加缓存步骤:
• npm:使用 actions/setup-node@v4,设置 cache: 'npm'
• 自定义路径:使用 actions/cache@v4
• Docker:设置 cache-from 和 cache-to - 4
步骤4: 验证缓存效果
检查缓存是否命中:
• 查看 cache-hit 输出变量(true 为精确命中)
• 对比构建时间(应减少 4-5 倍)
• 检查 Actions → Caches 页面确认缓存已存储 - 5
步骤5: 定期维护缓存
避免缓存问题:
• 监控缓存空间使用(上限 10GB)
• 定期清理旧缓存
• 遇到污染时更新 key 前缀强制重建
常见问题
缓存命中率只有 30% 怎么回事?
缓存了 10GB 以上会怎样?
• 分开缓存不同类型的依赖(npm、Docker、pip 各用一个 key)
• 定期在 Actions → Caches 页面手动删除无用缓存
• monorepo 项目考虑分仓库或使用 self-hosted runners
不同分支能共享缓存吗?
self-hosted runner 的缓存有什么不同?
缓存恢复失败构建会中断吗?
如何判断缓存是否需要更新?
• 依赖版本变化:hashFiles 自动处理,无需手动干预
• 缓存污染:构建突然失败,需要清除旧缓存
• 配置变更:如 Node 版本升级,需要在 key 中加入版本号
大多数情况下,正确配置后无需手动管理。
9 分钟阅读 · 发布于: 2026年4月7日 · 修改于: 2026年4月8日
相关文章
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
Supabase Auth 实战:邮箱验证、OAuth 与会话管理
GitHub Actions 部署策略:从 VPS 到云平台的 CD 流水线

评论
使用 GitHub 账号登录后即可评论