Gradle 构建缓存机制剖析

2.1 机制总览

Gradle 构建缓存通过复用先前构建的任务输出,避免相同输入下的重复计算,从而显著缩短增量构建与全量构建耗时。其核心由本地缓存、远程缓存以及缓存键生成规则三部分构成。缓存以内容寻址方式存储,每个缓存项对应一个唯一的键,该键由任务类型、输入属性、依赖坐标、构建环境等因素通过哈希算法生成。

Gradle 构建缓存整体流程 任务执行请求 计算缓存键(Key Hasher) 查询本地缓存 本地缓存命中 未命中→远程缓存 远程缓存命中/回填 恢复或执行任务

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