目錄
- 緩存設(shè)計需要考慮的地方
- 項目代碼編寫
- mybaits緩存設(shè)計原理
- guava緩存設(shè)計原理
本地緩存設(shè)計需要考慮的地方
- 數(shù)據(jù)結(jié)構(gòu)使用
- 對象上限
- 清除策略
- 過期時間
- 線程安全
- 是否持久化
本地緩存項目代碼編寫
- 提供公共的泛型抽象類
public interface Cache<K, V> {
V get(K key);
Map<K, V> getAll(List<K> keys);
void put(K key, V value);
void refresh(K key);
void refreshAll(List<K> keys);
}
public abstract class WrapLoadingCache<K, V> implements Cache<K, V>{
private static final Logger log = LoggerFactory.getLogger(WrapLoadingCache.class);
protected LoadingCache<K, Optional<V>> loaderCache;
protected abstract Optional<V> load(K key);
protected Map<K, Optional<V>> loadAll(List<K> keys){
throw new UnsupportedOperationException();
}
protected void init(int time, TimeUnit unit, int maxSize) {
loaderCache = CacheBuilder.newBuilder()
.expireAfterWrite(time, unit)
.maximumSize(maxSize)
.build(new CacheLoader<K, Optional<V>>() {
@Override
public Optional<V> load(K key) {
return LoaderCache.this.load(key);
}
@Override
public Map<K, Optional<V>> loadAll(Iterable<? extends K> keys) throws Exception {
return LoaderCache.this.loadAll(Lists.newArrayList(keys));
}
});
}
@Override
public V get(K key) {
Optional<V> opt = null;
try {
opt = loaderCache.get(key);
} catch (ExecutionException e) {
log.warn("WrapLoadingCacheget throw exception", e);
}
return opt != null && opt.isPresent() ? opt.get() : null;
}
@Override
public Map<K, V> getAll(List<K> keys) {
try {
Map<K, V> result = new HashMap<>();
ImmutableMap<K, Optional<V>> map = loaderCache.getAll(keys);
map.forEach((key, opt) -> {
V v = opt != null && opt.isPresent() ? opt.get() : null;
result.put(key, v);
});
return result;
} catch (ExecutionException e) {
log.warn("WrapLoadingCache get throw exception", e);
}
return null;
}
@Override
public void put(K key, V value) {
loaderCache.put(key, Optional.ofNullable(value));
}
@Override
public void refresh(K key) {
loaderCache.refresh(key);
}
@Override
public void refreshAll(List<K> keys) {
loaderCache.putAll(loadAll(keys));
}
}
- 各個功能如果要使用則,繼承WrapLoadingCache,并實現(xiàn)InitializingBean以便spring啟動時調(diào)用init方法,init方法可以傳入時間之類的過期設(shè)置參數(shù)。
- load方法各自子類自個設(shè)計, 本地緩存后可跟redis緩存redis總結(jié)中redis實踐有需要注意的redis緩存實踐參考。
mybaits緩存設(shè)計原理
緩存圖
-
其中Cache是接口,定義了一些公共方法,PerpetualCache是一級緩存,LruCache,SynchronizedCache, BlockingCache等類都是二級緩存,采用裝飾器模式,裝飾一級緩存。一級緩存是SqlSession級別的,二級緩存是Mapping級別的存在線程安全問題,當(dāng)然mybatis提供線程安全的Cache,但是mybatis處理二級緩存雖然解決了線程安全問題,并沒有提供RR的隔離級別,只提供RC。
緩存.png
LruCache
- LruCache 的 keyMap 屬性是實現(xiàn) LRU 策略的關(guān)鍵,該屬性類型繼承自 LinkedHashMap,并覆蓋了 removeEldestEntry 方法。LinkedHashMap 內(nèi)部的 tail 節(jié)點會指向最新插入的節(jié)點。head 節(jié)點則指向第一個被插入的鍵值對,也就是最久未被訪問的那個鍵值對。默認(rèn)情況下,LinkedHashMap 僅維護(hù)鍵值對的插入順序。LinkedHashMap具體實現(xiàn)Lru細(xì)節(jié)可參考LinkedHashMap 源碼詳細(xì)分析
BlockingCache
- getObject 方法會先獲取與 key 對應(yīng)的鎖,并加鎖。若緩存命中,getObject 方法會釋放鎖,否則將一直鎖定。getObject 方法若返回 null,表示緩存未命中。
- 使用分段鎖的思想,一個key對應(yīng)一個ReentrantLock
private final ConcurrentHashMap<Object, ReentrantLock> locks;
mybatis二級緩存導(dǎo)致的不可重復(fù)讀問題
- 可參考MyBatis 源碼分析 - 緩存原理5.二級緩存
guava緩存設(shè)計原理
- 并發(fā)方面的實現(xiàn)類似ConcurrentHashMap1.7的分段鎖(分段Segment),LoadingCache寫法的實現(xiàn)在get時無數(shù)據(jù)會自動調(diào)用load方法。LoadingCache支持自動刷新。具體細(xì)節(jié)可參考Guava LocalCache 緩存介紹及實現(xiàn)源碼深入剖析
// CacheLoader形式的Cache
private static final LoadingCache<String, String> LOADER_CACHE = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.SECONDS)
.maximumSize(1000)
.build(new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return key + new Date();
}
});
