Gradle 构建缓存机制剖析
2.1 机制总览
Gradle 构建缓存通过复用先前构建的任务输出,避免相同输入下的重复计算,从而显著缩短增量构建与全量构建耗时。其核心由本地缓存、远程缓存以及缓存键生成规则三部分构成。缓存以内容寻址方式存储,每个缓存项对应一个唯一的键,该键由任务类型、输入属性、依赖坐标、构建环境等因素通过哈希算法生成。
2.2 本地缓存与远程缓存
| 缓存类型 | 存储位置 | 共享范围 | 默认启用 | 适用场景 |
|---|---|---|---|---|
| 本地缓存 | ~/.gradle/caches/build-cache-1/ |
单台开发/构建机 | 是(可通过 --no-build-cache 关闭) |
个人开发环境、CI 单节点 |
| 远程缓存 | HTTP(s) 服务器(如 Artifactory、Nexus、自定义服务) | 团队/跨环境共享 | 否(需配置) | 多开发者协作、CI 集群 |
最佳实践:大型项目推荐同时启用本地与远程缓存,本地负责近期构建加速,远程负责跨作业/跨节点复用。
2.3 缓存键生成规则
缓存键是 32 位十六进制字符串,通过对任务所有缓存相关输入进行 SHA-256 哈希得到。完整的输入包括:
- 任务类型全限定名(如
org.gradle.api.tasks.compile.JavaCompile) - 任务类路径指纹(任务本身及其依赖类的字节码哈希)
- 所有标注了
@Input、@InputFile、@InputDirectory的属性值或内容哈希 - 输出文件名/路径模板(相对项目目录)
- Gradle 版本与构建环境标识(由
gradleUserHome与操作系统信息派生) - 构建缓存目标体系架构(如
org.gradle.cache.internal.DefaultCacheKey中记录的 OS 与架构)
以下代码展示了如何通过 Gradle 构建扫描或日志查看缓存键的生成过程:
// 在 build.gradle 中为任务显式声明输入缓存
task customTask {
inputs.property("version", "1.0.0")
inputs.file("config/settings.xml")
outputs.file("$buildDir/output.txt")
doLast {
// 任务逻辑
}
}
调试时可通过
--info 日志级别查看缓存键计算细节:gradle assemble --info 2>&1 | grep -i "cache key"
# 输出示例:
# [org.gradle.cache.internal.DefaultCacheKey] Cache key for task :compileJava: a3f8b2c9d0e1f4a7b6c5d4e3f2a1b0c9
2.4 缓存存储结构
本地缓存目录采用两级目录散列存储:第一级取缓存键的前两个字符,第二级取键的剩余字符。每个缓存条目包含:
- 元数据文件
.metadata:记录创建时间、任务类型、输入哈希、生成器版本。 - 归档文件
.tar:任务的所有输出文件打包而成。 - 清理索引
cache.properties:记录最近访问时间、大小等用于 LRU 清理。
远程缓存通常以同样键结构存储,Gradle 通过 HTTP PUT/GET 操作与远程仓库交互,请求体为 tar 归档,响应体为 tar 归档或 404 表示未命中。
# 本地缓存目录结构示例
~/.gradle/caches/build-cache-1/
├── a3/
│ ├── f8b2c9d0e1f4a7b6c5d4e3f2a1b0c9/
│ │ ├── build-973fc5a7-8f2b-4f7d-9e3a-2c1b0d8e6f4a.tar
│ │ └── f8b2c9d0e1f4a7b6c5d4e3f2a1b0c9.metadata
│ └── ...
└── gc.properties
2.5 配置与命令
启用远程缓存(gradle.properties):
org.gradle.caching=true
org.gradle.cache.tasks=true
org.gradle.cache.tasks.remote.enabled=true
org.gradle.cache.tasks.remote.url=http://your-cache-server:8080/cache/
org.gradle.cache.tasks.remote.push=false # 默认仅拉取,需 push 可设为 true
命令行控制:
# 禁用构建缓存
gradle build --no-build-cache
# 强制远程推送
gradle build --build-cache --remote-build-cache-push
# 查看缓存统计
gradle build --build-cache --info
# 手动清理本地缓存
gradle cleanBuildCache
2.6 调试与常见错误
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 缓存从未命中 | 输入非确定性(如时间戳、随机数、瞬态文件路径) | 使用相对路径、排除非常量输入;添加 @IgnoreInput 注释 |
| 远程缓存上传失败 | 权限不足或远程服务器不允许 PUT | 检查远程仓库配置、认证凭据;设置 org.gradle.cache.tasks.remote.push=false 暂时禁用推送 |
| 本地缓存占用过大 | 未设置最大缓存大小 | 在 gradle.properties 中设置 org.gradle.cache.tasks.maxAge=7d 或 maxCacheSize=1GB |
注意:Gradle 不会自动清理远程缓存,需运维侧配置保留策略(如保留最近 30 天的缓存条目)。
2.7 性能与稳定性优化
- 缓存键稳定性:避免使用路径、环境变量、不可复现的派生值。任务输入应尽可能使用
@PathSensitive(PathSensitivity.RELATIVE)注解。 - 并行拉取:远程缓存支持并发下载,可通过
org.gradle.cache.tasks.remote.parallelDownload=true启用。 - 预热缓存:在 CI Master 分支构建后执行
gradle assemble --build-cache --remote-build-cache-push填充远程缓存。 - 本地缓存分层:对于大型 monorepo,可配置多个本地缓存目录并借助符号链接实现读写分离。
# 多级本地缓存配置示例(gradle.properties)
org.gradle.cache.tasks.localStore=/fast-ssd/.gradle-cache
org.gradle.cache.tasks.localStore.fallback=/hdd/.gradle-cache-fallback