TL;DR
- 场景: 线上项目广泛使用 Guava Cache,但对 LocalCache / Segment / LoadingCache 具体行为缺乏源码级认知
- 结论: Guava 通过 LocalCache+Segment 分段结构、引用队列和访问/写入队列,实现并发安全的本地缓存、过期与刷新策略
- 产出: 一份围绕体系类图、put/get 流程与过期重载机制的工程化笔记
体系类图
Guava Cache 的核心类结构:
- CacheBuilder: 缓存构建器,指定配置参数并初始化本地缓存
- CacheLoader: 抽象类,用于从数据源加载数据
- Cache: 接口,定义 get、put、invalidate 等操作
- LoadingCache: 接口,继承自 Cache,定义 get、getUnchecked、getAll 等操作
- LocalCache: 核心类,包含数据结构与基本缓存操作方法
- LocalManualCache: LocalCache 内部静态类,实现 Cache 接口
- LocalLoadingCache: LocalCache 内部静态类,继承 LocalManualCache,实现 LoadingCache 接口
LocalCache 核心结构
LocalCache 为 Guava Cache 的核心类,数据结构与 ConcurrentHashMap 相似,由多个 Segment 组成:
主要字段:
Segment<K, V>[] segments: Map 的数组int concurrencyLevel: 并发量,即 segments 数组大小long expireAfterAccessNanos: 访问后的过期时间long expireAfterWriteNanos: 写入后的过期时间long refreshNanos: 刷新时间
Segment 组成:
AtomicReferenceArray<ReferenceEntry<K, V>> table: 哈希表Queue<ReferenceEntry<K, V>> writeQueue: 写入顺序队列Queue<ReferenceEntry<K, V>> accessQueue: 访问顺序队列
Put 流程分析
- 上锁: 加锁保证当前 Segment 内 put 操作的线程安全
- 清理队列元素: 清理 keyReferenceQueue 和 valueReferenceQueue 两个引用队列
- setValue: 将 value 写入到 Entry
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
long now = map.ticker.read();
preWriteCleanup(now);
} finally {
unlock();
postWriteCleanup();
}
}
Get 流程分析
- 获取对象引用(可能是非 alive 状态)
- 判断对象引用是否是 alive 的
- 如果是 alive 的且设置 refresh,则异步刷新查询 value
- 针对不是 alive 但正在 loading 的,等待 loading 完成
- 如果 value 还未拿到,则查询 loader 方法获取对应值
V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
int hash = hash(checkNotNull(key));
return segmentFor(hash).get(key, hash, loader);
}
过期重载机制
数据过期不会自动重载,而是通过 get 操作时执行过期重载。
LocalLoadingCache 核心方法:
get(K key): 不存在则通过 CacheLoader 加载getUnchecked(K key): 不声明受检异常getAll(Iterable<? extends K> keys): 批量加载refresh(K key): 主动刷新
错误速查
| 症状 | 根因定位 | 修复 |
|---|---|---|
| 缓存偶发读到旧数据 | 仅配置 expireAfterWrite/Access,依赖懒失效 | 配置 refreshAfterWrite 或显式 refresh |
| size() 在高并发下明显跳动 | Segment.count + modCount 设计为弱一致快照 | 避免用 size() 做容量/限流判断 |
| QPS 突增时偶发长尾请求 | CacheLoader 执行时间长 | 优化数据源查询或引入本地合并加载 |
| JVM 堆占用持续升高 | 未设置 maximumSize/maximumWeight | 明确设置容量与权重策略 |