1,領域模型設計

該設計方案定義了三個基礎接口: Cache,Cleanable和CacheManager;和一個默認實現類DefaultCacheManager。
- Cache接口抽象了非內存緩存所能提供的基礎操作,Cache接口隔離了外部緩存的具體實現方案,可以是Redis/Codis等任意形式的緩存方案;
- Cleanable接口定義了被緩存對象(value)的基本屬性,所以需要被緩存的對象都必須實現Cleanable接口;
- CacheManager抽象了該緩存方案支持的功能,直接提供給客戶端調用,屏蔽了具體緩存實現方案,允許提供多個解決方案;
- DefaultCacheManager是CacehManager的實現類,支持使用本地內存和外部緩存的實現。
2,Cache interface

該接口的定義沒有多少爭議,就是實現序列化對象通過key進行存取。
3,Cleanable interface

前文已說明,所有需要被緩存的對象都必須實現該接口。接口定義了,緩存對象必須是可清理的,有過期時間: expiredTime,創建時間: createdTime;如果使用的是本地內存實現,則會通過isValid方法方便的檢查對象是否過期,否則使用timeout方法來設置外部緩存時長。
4,CacheManager interface

CacheManager接口是真正面向客戶端使用的,緩存的value是Cleanable對象,key允許是任何可序列化值(常用String);特別地,需要攜帶一個枚舉類ModuleType。在微服務或分布式的架構系統中,無論是在項目初期或中后期,都可能公用同一個外部緩存服務器,因此為了避免緩存數據沖突和方便數據追蹤,都需要對Key進行模塊分割。
5,DefaultCacheManager
public class DefaultCacheManager implements CacheManager, Runnable {
private static ConcurrentMap<String, Cleanable> dataMap = new ConcurrentHashMap<String, Cleanable>();
private final Long cleanPeriod;
private final boolean enabledRedisCache;
private final ScheduledExecutorService validationService;
private final Cache cacheClient;
public DefaultCacheManager() {
this(1000L, null);
}
public DefaultCacheManager(Cache cacheClient) {
this(null, cacheClient);
}
public DefaultCacheManager(Long cleanPeriod) {
this(cleanPeriod, null);
}
/**
* @param cleanPeriod
* @param cacheClient
*/
private DefaultCacheManager(Long cleanPeriod, Cache cacheClient) {
this.enabledRedisCache = cacheClient != null ? true : false;
this.cacheClient = cacheClient;
this.cleanPeriod = cleanPeriod;
if(!enabledRedisCache) {
/**
* 在本地完成expired清理動作
*/
this.validationService = Executors.newSingleThreadScheduledExecutor();
this.validationService.scheduleAtFixedRate(this, this.cleanPeriod,
this.cleanPeriod, TimeUnit.MILLISECONDS);
} else
this.validationService = null;
}
@Override
public boolean exist(Serializable srcKey, ModuleType keyType) {
String key = keyType.key(srcKey);
if(enabledRedisCache) {
return cacheClient.getValue(key) != null;
}
Cleanable c = dataMap.get(key);
if (c == null)
return false;
if (!c.isValid()) {
dataMap.remove(key);
return false;
}
return true;
}
@Override
public void put(Serializable srcKey, ModuleType keyType, Cleanable value) {
String key = keyType.key(srcKey);
if(enabledRedisCache) {
cacheClient.setValue(key, value, value.timeout(), TimeUnit.SECONDS);
return;
}
dataMap.put(key, value);
}
@Override
public Cleanable get(Serializable srcKey, ModuleType keyType) {
String key = keyType.key(srcKey);
Cleanable value;
if(enabledRedisCache) {
value = ((Cleanable) cacheClient.getValue(key));
} else {
value = dataMap.get(key);
}
return value;
}
@Override
public void remove(Serializable srcKey, ModuleType keyType) {
String key = keyType.key(srcKey);
if(enabledRedisCache) {
cacheClient.removeValue(key);
} else {
dataMap.remove(key);
}
}
public void run() {
/**
* 清理過期對象
*/
Iterator<String> iter = dataMap.keySet().iterator();
while (iter.hasNext()){
String key = iter.next();
// 由於並發緣故
// 可能已經把該{@param key}對應的對象, 清理掉了
Cleanable c = dataMap.get(key);
if(c != null && !c.isValid()){
iter.remove();
}
}
}
} |
DefaultCacheManager類是支持內存緩存和外部緩存的默認實現類。在此,可能會有疑惑,像Redis這樣的外部緩存,應用已經非常普遍,為什么還要做內存緩存?我個人有三個考慮:1)即便是開源項目,如Eurka Server也仍然使用內存緩存,具體原因可查閱該項目的源碼設計分析;2)對於微服務或分布式架構的系統,在項目早期或對於某些服務而言,需緩存數據量小或不需要共享緩存數據;3)追求高響應時間,不想額外依賴外部服務,提高系統可靠性。
6,一個實現Cleanable接口的案例
/** |
如上代碼所示,Piece類是一個可緩存對象的實現類,其意義用於緩存所有請求下載過該資源片的設備節點列表。
a, 仔細看Piece類的屬性有一個明顯的特征,即所有屬性都用final關鍵字修飾了;而實際上,所有Cleanable接口的實現類(或會被緩存的對象)的字段,除非必須的,都應當使用final關鍵字修飾。因為我們服務處在並發環境下,緩存對象應當盡可能做到線程安全,final修飾的作用就是消除JMM的重排序,保證創建的對象是線程安全的;而如果被緩存對象存在非final修飾的屬性,則在發生修改時,若要確保數據的一致性,則必須加鎖。
b, 另外,可以注意到Piece類有兩個構造方法,一個是公開訪問,一個是不可公開調用但可以通過反射工具調用的;所以,所有Cleanable的實現類都必須實現一個@JsonCreator注解的保留構造方法,如此才能實現自動的序列化和反序列化。
