为了加速 CI 的执行,缓存是非常有效的手段。保证缓存的最高利用率,是使用缓存时最需要关注的点。比如在把整个 target
目录缓存后,何时更新这个缓存?最好的方式是在有依赖变更时,对于 Rust 来说就是 Cargo.lock
有变化,对于 Node 来说就是 package.lock
有变化。
下面我们看看如何利用 cache 这个组件来实现上述效果,主要有三个的参数:
key
缓存 ID,可以把整个缓存空间看出是一个 KV 对path
即缓存哪些路径restore-keys
这里指定了当key
没有命中时,可以选择的缓存
下面用缓存 Rust 项目作为示例进行讲解:
|
|
首先看 key
的定义,分为四个部分,分别是:
- 固定值,
debug
为了与release
编译区分开 - 变量,操作系统
- toolchain 文件 Hash
- Cargo.lock 文件 Hash
对于一个 cache 来说,当使用 key
命中时,称为 cache hit
,这时在 Actions 结束时,不需要更新 cache。因此设计这个 key 时,需要注意两点:
- key 要能够表示缓存变更,上面四个变量即可以确定一个完整有效缓存
- 能够表示缓存变更的字段可能有多个,经常变动的需要放在最后面,上面四个变量就遵循了这个顺序
第二点的设计需要与 restore-key
一起来看。当 Actions 执行时,如果 key
没有不一致,这是说明缓存内容有变,可能是 lock 文件变了,也可能是 toolchain,按照 cache 一般设计来说,key 不一致,是没法使用缓存的。
但这时缓存还是可以用的,比如项目中有 10 个依赖,只是对其中一个做了升级,那么其余 9 个的缓存还是有效的,这时缓存的选取就用到 restore-key
了:
restore-key 指定一系列候选的缓存 key,用作没有命中 key 时的候补缓存
由于候补缓存的方式是从上到下,所以 restore-key
的长度一般都是依次递减。对上面的例子来说,缓存选择方式如下:
- 如果只是 Cargo.lock 变更了,那么就用第一个
restore-key
指向的缓存,因为它的缓存有效率最高 - 如果 Cargo.lock 与 toolchain 文件都变更了,那么就用第二个,原因同上
- 如果当前是 release 编译,不管 Cargo.lock、toolchain 文件有无变更,都无法利用缓存,这样可以避免与 debug 的 cache 混淆,导致 cache 过大
只要 key
没有命中,在 Actions 执行结束后,就会去更新缓存,便于下次直接命中。可以看到,通过 restore-key
的巧妙设计,可以保证当前最有效的缓存一直是最“热”的。
注意事项
为了安全、成本,GitHub 对缓存做了如下几点的限制:
- 如果缓存是在 main 分支上产生,那么所有 main 分支衍生的其他分支都可以用到;但是,同时由 main 衍生的分支 B1、B2,他们产生的缓存,是不能共享的
- 对于 7 天没有访问的缓存,会被自动删掉
- 缓存空间只有 10G,超过会按照访问时间 LRU 淘汰