畢竟西湖六月中,風光不與四時同。java
接天蓮葉無窮碧,映日荷花別樣紅。git
曉出淨慈寺送林子方-楊萬里redis
週末與小夥伴約了一波西湖,這個時間荷花開的正好...,在開始文章以前先放一張「佛系」美圖來鎮樓!!!數據庫
最近這段時間用了下谷歌的guava,本身封了一個緩存模板方案,特此記錄,以備後續所需。緩存
爲何要從這個來講起,由於不說這個就沒guava什麼事了!數據結構
最近項目中須要使用緩存來對一查查詢頻繁的數據作緩存處理;首先咱們也不但願引入三方的如redis或者memcache這樣的服務進來,其次是咱們對於數據一致性的要求並非很高,不須要集羣內的查詢接口共享到一份緩存數據;因此這樣一來咱們只要實現一個基於內存的緩存便可。app
最開始我並無考慮使用guava來作這個事情,而是本身寫了一套基於CurrentHashMap的緩存方案;這裏須要明確一點,由於緩存在這個場景裏面但願提供超時清除的能力,而基於因此在本身緩存框架中增長了定時清除過時數據的能力。框架
這裏我就直接把定時清楚的這段代碼放上來:ide
/** * 靜態內部類來進行超時處理 */
private class ClearCacheThread extends Thread {
@Override
public void run() {
while (true){
try {
long now = System.currentTimeMillis();
Object[] keys = map.keySet().toArray();
for (Object key : keys) {
CacheEntry entry = map.get(key);
if (now - entry.time >= cacheTimeout) {
synchronized (map) {
map.remove(key);
if (LOGGER.isDebugEnabled()){
LOGGER.debug("language cache timeout clear");
}
}
}
}
}catch (Exception e){
LOGGER.error("clear out time cache value error;",e);
}
}
}
}
複製代碼
這個線程是用來單獨處理過時數據的。緩存初始化時就會觸發這個線程的start方法開始執行。函數
正式因爲這段代碼的不合理致使我在發佈dev環境以後,機器GC觸發的頻次高的離譜。在嘗試了不一樣的修復方案以後,最後選擇放棄了;改用guava了!
小夥伴們能夠在下面留言來討論下這裏爲何會存在頻繁GC的問題;我會把結論放在評論回覆裏面。
爲何選用guava呢,很顯然,是大佬推薦的!!!
guava是谷歌提供的一個基於內存的緩存工具包,Guava Cache 提供了一種把數據(key-value對)緩存到本地(JVM)內存中的機制,適用於不多會改動的數據。Guava Cache 與 ConcurrentMap 很類似,但也不徹底同樣。最基本的區別是 ConcurrentMap 會一直保存全部添加的元素,直到顯式地移除。相對地,Guava Cache 爲了限制內存佔用,一般都設定爲自動回收元素。
對於咱們的場景,guava 提供的能力知足了咱們的須要:
既然選擇它了,咱們仍是有必要來先對它有個大體的瞭解;先來看看它提供的一些類和接口:
接口/類 | 詳細解釋 |
---|---|
Cache | 【I】;定義get、put、invalidate等操做,這裏只有緩存增刪改的操做,沒有數據加載的操做。 |
AbstractCache | 【C】;實現Cache接口。其中批量操做都是循環執行單次行爲,而單次行爲都沒有具體定義。 |
LoadingCache | 【I】;繼承自Cache。定義get、getUnchecked、getAll等操做,這些操做都會從數據源load數據。 |
AbstractLoadingCache | 【C】;繼承自AbstractCache,實現LoadingCache接口。 |
LocalCache | 【C】;整個guava cache的核心類,包含了guava cache的數據結構以及基本的緩存的操做方法。 |
LocalManualCache | 【C】;LocalCache內部靜態類,實現Cache接口。其內部的增刪改緩存操做所有調用成員變量localCache(LocalCache類型)的相應方法。 |
LocalLoadingCache | 【C】;LocalCache內部靜態類,繼承自LocalManualCache類,實現LoadingCache接口。其全部操做也是調用成員變量localCache(LocalCache類型)的相應方法 |
CacheBuilder | 【C】;緩存構建器。構建緩存的入口,指定緩存配置參數並初始化本地緩存。CacheBuilder在build方法中,會把前面設置的參數,所有傳遞給LocalCache,它本身實際不參與任何計算 |
CacheLoader | 【C】;用於從數據源加載數據,定義load、reload、loadAll等操做。 |
整個來看的話,guava裏面最核心的應該算是 LocalCache 這個類了。
@GwtCompatible(emulated = true)
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> 複製代碼
關於這個類的源碼這裏就不細說了,直接來看下在實際應用中個人封裝思路【封裝知足我當前的需求,若是有小夥伴須要借鑑,能夠本身在作擴展】
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 時間單位(分鐘) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的時間 */
private Date resetTime;
/** 分別記錄歷史最多緩存個數及時間點*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
複製代碼
這裏先是定義了一些常量和基本的屬性信息,固然這些屬性會提供set&get方法,供實際使用時去自行設置。
public LoadingCache<K, V> getCache() {
//使用雙重校驗鎖保證只有一個cache實例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的構造函數是私有的,只能經過其靜態方法newBuilder()來得到CacheBuilder的實例
cache = CacheBuilder.newBuilder()
//設置緩存容器的初始容量爲100
.initialCapacity(DEFAULT_SIZE)
//緩存數據的最大條目
.maximumSize(maxSize)
//定時回收:緩存項在給定時間內沒有被寫訪問(建立或覆蓋),則回收。
.expireAfterWrite(expireTime, timeUnit)
//啓用統計->統計緩存的命中率等
.recordStats()
//設置緩存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
LOGGER.debug("{} was removed, cause is {}" ,notification.getKey(), notification.getCause());
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
LOGGER.info("本地緩存{}初始化成功.", this.getClass().getSimpleName());
}
}
}
}
return cache;
}
複製代碼
上面這段代碼是整個緩存的核心,經過這段代碼來生成咱們的緩存對象【使用了單例模式】。具體的屬性參數看註釋。
由於上面的那些都是封裝在一個抽象類AbstractGuavaCache裏面的,因此我又封裝了一個CacheManger用來管理緩存,並對外提供具體的功能接口;在CacheManger中,我使用了一個靜態內部類來建立當前默認的緩存。
/** * 使用靜態內部類實現一個默認的緩存,委託給manager來管理 * * DefaultGuavaCache 使用一個簡單的單例模式 * @param <String> * @param <Object> */
private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/** * 處理自動載入緩存,按實際狀況載入 * 這裏 * @param key * @return */
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
複製代碼
大概思路就是這樣,若是須要擴展,咱們只須要按照實際的需求去擴展AbstractGuavaCache這個抽象類就能夠了。具體的代碼貼在下面了。
public abstract class AbstractGuavaCache<K, V> {
protected final Logger LOGGER = LoggerFactory.getLogger(AbstractGuavaCache.class);
private static final int MAX_SIZE = 1000;
private static final int EXPIRE_TIME = 10;
/** 用於初始化cache的參數及其缺省值 */
private static final int DEFAULT_SIZE = 100;
private int maxSize = MAX_SIZE;
private int expireTime = EXPIRE_TIME;
/** 時間單位(分鐘) */
private TimeUnit timeUnit = TimeUnit.MINUTES;
/** Cache初始化或被重置的時間 */
private Date resetTime;
/** 分別記錄歷史最多緩存個數及時間點*/
private long highestSize = 0;
private Date highestTime;
private volatile LoadingCache<K, V> cache;
public LoadingCache<K, V> getCache() {
//使用雙重校驗鎖保證只有一個cache實例
if(cache == null){
synchronized (this) {
if(cache == null){
//CacheBuilder的構造函數是私有的,只能經過其靜態方法ne
//wBuilder()來得到CacheBuilder的實例
cache = CacheBuilder.newBuilder()
//設置緩存容器的初始容量爲100
.initialCapacity(DEFAULT_SIZE)
//緩存數據的最大條目
.maximumSize(maxSize)
//定時回收:緩存項在給定時間內沒有被寫訪問
//(建立或覆蓋),則回收。
.expireAfterWrite(expireTime, timeUnit)
//啓用統計->統計緩存的命中率等
.recordStats()
//設置緩存的移除通知
.removalListener((notification)-> {
if (LOGGER.isDebugEnabled()){
//...
}
})
.build(new CacheLoader<K, V>() {
@Override
public V load(K key) throws Exception {
return fetchData(key);
}
});
this.resetTime = new Date();
this.highestTime = new Date();
if (LOGGER.isInfoEnabled()){
//...
}
}
}
}
return cache;
}
/** * 根據key從數據庫或其餘數據源中獲取一個value,並被自動保存到緩存中。 * * 改方法是模板方法,子類須要實現 * * @param key * @return value,連同key一塊兒被加載到緩存中的。 */
protected abstract V fetchData(K key);
/** * 從緩存中獲取數據(第一次自動調用fetchData從外部獲取數據),並處理異常 * @param key * @return Value * @throws ExecutionException */
protected V getValue(K key) throws ExecutionException {
V result = getCache().get(key);
if (getCache().size() > highestSize) {
highestSize = getCache().size();
highestTime = new Date();
}
return result;
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public TimeUnit getTimeUnit() {
return timeUnit;
}
public void setTimeUnit(TimeUnit timeUnit) {
this.timeUnit = timeUnit;
}
public Date getResetTime() {
return resetTime;
}
public void setResetTime(Date resetTime) {
this.resetTime = resetTime;
}
public long getHighestSize() {
return highestSize;
}
public void setHighestSize(long highestSize) {
this.highestSize = highestSize;
}
public Date getHighestTime() {
return highestTime;
}
public void setHighestTime(Date highestTime) {
this.highestTime = highestTime;
}
}
複製代碼
public class DefaultGuavaCacheManager {
private static final Logger LOGGER =
LoggerFactory.getLogger(DefaultGuavaCacheManager.class);
//緩存包裝類
private static AbstractGuavaCache<String, Object> cacheWrapper;
/** * 初始化緩存容器 */
public static boolean initGuavaCache() {
try {
cacheWrapper = DefaultGuavaCache.getInstance();
if (cacheWrapper != null) {
return true;
}
} catch (Exception e) {
LOGGER.error("Failed to init Guava cache;", e);
}
return false;
}
public static void put(String key, Object value) {
cacheWrapper.getCache().put(key, value);
}
/** * 指定緩存時效 * @param key */
public static void invalidate(String key) {
cacheWrapper.getCache().invalidate(key);
}
/** * 批量清除 * @param keys */
public static void invalidateAll(Iterable<?> keys) {
cacheWrapper.getCache().invalidateAll(keys);
}
/** * 清除全部緩存項 : 慎用 */
public static void invalidateAll() {
cacheWrapper.getCache().invalidateAll();
}
public static Object get(String key) {
try {
return cacheWrapper.getCache().get(key);
} catch (Exception e) {
LOGGER.error("Failed to get value from guava cache;", e);
}
return null;
}
/** * 使用靜態內部類實現一個默認的緩存,委託給manager來管理 * * DefaultGuavaCache 使用一個簡單的單例模式 * @param <String> * @param <Object> */
private static class DefaultGuavaCache<String, Object> extends AbstractGuavaCache<String, Object> {
private static AbstractGuavaCache cache = new DefaultGuavaCache();
/** * 處理自動載入緩存,按實際狀況載入 * @param key * @return */
@Override
protected Object fetchData(String key) {
return null;
}
public static AbstractGuavaCache getInstance() {
return DefaultGuavaCache.cache;
}
}
}
複製代碼