摘要 Guava Cache是Google開源的Java工具集庫Guava里的一款緩存工具,一直覺得使用起來比較簡單,沒想到這次居然還踩了一個坑 背景 功能需求抽象出來很簡單,就是將資料庫的查詢 的結果緩存起來。但同時還有批量請求,為了提高效率,肯定要批量查詢資料庫, 對於的guava cache ...
摘要
Guava Cache是Google開源的Java工具集庫Guava里的一款緩存工具,一直覺得使用起來比較簡單,沒想到這次居然還踩了一個坑
背景
功能需求抽象出來很簡單,就是將資料庫的查詢sthMapper.findById(Long id)
的結果緩存起來。但同時還有批量請求,為了提高效率,肯定要批量查詢資料庫,sthMapper.findByIds(Collection<Long> ids)
對於的guava cache 處理類
// 定義guava緩存
public SthCache() {
sthCache = CacheBuilder.newBuilder()
.maximumSize(SIZE)
.refreshAfterWrite(3, TimeUnit.SECONDS)
.build(new CacheLoader<Long, List<Long>>() {
@Override
public List<Long> load(final Long id) {
return doLoad(Arrays.asList(id)).get(id);
}
@Override
public Map<Long, List<Long>> loadAll(
final Iterable<? extends Long> ids)
throws Exception {
return doLoad(Lists.newArrayList(ids));
}
});
}
// 實際從資料庫中載入數據
private Map<Long, List<Long>> doLoad(final List<Long> ids) {
return sthMapper.findByIds(ids);
}
// 批量獲取數據
public Map<Long, List<Long>> getSthById(final List<Long> ids) {
return sthCache.getAll(ids);
}
沒毛病,getAll(Iterable<? extendsK>)方法用來執行批量查詢。預設情況下,對每個不在緩存中的鍵,getAll方法會單獨調用CacheLoader.load來載入緩存項。如果批量的載入比多個單獨載入更高效,你可以重載CacheLoader.loadAll來利用這一點。getAll(Iterable)的性能也會相應提升。這邊定義了loadAll效率高了。
問題
在debug的時候發現確實走的loadAll,批量查詢資料庫。但是上線後,線上監控數據卻發現這個介面耗時很長,通過分析,發現有很多sthMappper.findByIds()的單個查詢。也就是說,並沒有調用loadAll
,走到批量查詢資料庫中。
分析解決
- 首先看了下guava的代碼實現
ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
int hits = 0;
int misses = 0;
// 省略一大坨
try {
if (!keysToLoad.isEmpty()) {
try {
// 調用loadAll
Map<K, V> newEntries = loadAll(keysToLoad, defaultLoader);
批量查詢時,對於沒有命中的,確實調用的loadAll來載入數據的。
那問題就不是這邊了。在load()
方法打了個斷點,原因就找到了。
原來是refesh的時候,載入的。refreshAfterWrite
刷新緩存數據時調用的還是load方法。
搜索了下,https://github.com/google/guava/issues/1975 github上這個issue還在。汗!!!
最後我這邊解決是用Spring Cache
統一了緩存管理。
總結
對於開源庫的使用不可只知其然,不知其所以然。
關註公眾號【方丈的寺院】,第一時間收到文章的更新,與方丈一起開始技術修行之路