寫了那么多,終于到Store了。Store是EHCache中Element管理的核心,所有的Element都存放在Store中,也就是說Store用于所有和Element相關的處理。
EHCache中的Element
在EHCache中,它將所有的鍵值對抽象成一個Element,作為面向對象的設計原則,把數據和操作放在一起,Element除了包含key、value屬性以外,它還加入了其他和一個Element相關的統計、配置信息以及操作:
public class Element implements Serializable, Cloneable {
//the cache key. 從1.2以后不再強制要求Serializable,因為如果只是作為內存緩存,則不需要對它做序列化。IgnoreSizeOf注解表示在做SizeOf計算時key會被忽略。
@IgnoreSizeOf
private final Object key;
//the value. 從1.2以后不再強制要求Serializable,因為如果只是作為內存緩存,則不需要對它做序列化。
private final Object value;
//version of the element. 這個屬性只是作為紀錄信息,EHCache實際代碼中并沒有用到,用戶代碼可以通過它來實現不同版本的處理問題。默認值是1。
//如果net.sf.ehcache.element.version.auto系統屬性設置為true,則當Element加入到Cache中時會被更新為當前系統時間。此時,用戶設置的值會丟失。
private volatile long version;
//The number of times the element was hit.命中次數,在每次查找到一個Element會加1。
private volatile long hitCount;
//The amount of time for the element to live, in seconds. 0 indicates unlimited. 即一個Element自創建(CreationTime)以后可以存活的時間。
private volatile int timeToLive = Integer.MIN_VALUE;
//The amount of time for the element to idle, in seconds. 0 indicates unlimited. 即一個Element自最后一次被使用(min(CreationTime,LastAccessTime))以后可以存活的時間。
private volatile int timeToIdle = Integer.MIN_VALUE;
//Pluggable element eviction data instance,它存儲這個Element的CreationTime、LastAccessTime等信息,竊以為這個抽取成一個單獨的類沒什么理由,而且這個類的名字也不好。
private transient volatile ElementEvictionData elementEvictionData;
//If there is an Element in the Cache and it is replaced with a new Element for the same key,
//then both the version number and lastUpdateTime should be updated to reflect that. The creation time
//will be the creation time of the new Element, not the original one, so that TTL concepts still work. 在put和replace操作中該屬性會被更新。
private volatile long lastUpdateTime;
//如果timeToLive和timeToIdle沒有手動設置,該值為true,此時在計算expired時使用CacheConfiguration中的timeTiLive、timeToIdle的值,否則使用Element自身的值。
private volatile boolean cacheDefaultLifespan = true;
//這個ID值用于EHCache內部,但是暫時不知道怎么用。
private volatile long id = NOT_SET_ID;
//判斷是否expired,這里如果timeToLive、timeToIdle都是Integer.MIN_VALUE時返回false,當他們都是0時,isEternal返回true
public boolean isExpired() {
if (!isLifespanSet() || isEternal()) {
return false;
}
long now = System.currentTimeMillis();
long expirationTime = getExpirationTime();
return now > expirationTime;
}
//expirationTime算法:如果timeToIdle沒有設置,或設置了,但是該Element還沒有使用過,取timeToLive計算出的值;如果timeToLive沒有設置,則取timeToIdle計算出的值,
//否則,取他們的最小值。
public long getExpirationTime() {
if (!isLifespanSet() || isEternal()) {
return Long.MAX_VALUE;
}
long expirationTime = 0;
long ttlExpiry = elementEvictionData.getCreationTime() + TimeUtil.toMillis(getTimeToLive());
long mostRecentTime = Math.max(elementEvictionData.getCreationTime(), elementEvictionData.getLastAccessTime());
long ttiExpiry = mostRecentTime + TimeUtil.toMillis(getTimeToIdle());
if (getTimeToLive() != 0 && (getTimeToIdle() == 0 || elementEvictionData.getLastAccessTime() == 0)) {
expirationTime = ttlExpiry;
} else if (getTimeToLive() == 0) {
expirationTime = ttiExpiry;
} else {
expirationTime = Math.min(ttlExpiry, ttiExpiry);
}
return expirationTime;
}
//在將Element加入到Cache中并且它的timeToLive和timeToIdle都沒有設置時,它的timeToLive和timeToIdle會根據CacheConfiguration的值調用這個方法更新。
protected void setLifespanDefaults(int tti, int ttl, boolean eternal) {
if (eternal) {
this.timeToIdle = 0;
this.timeToLive = 0;
} else if (isEternal()) {
this.timeToIdle = Integer.MIN_VALUE;
this.timeToLive = Integer.MIN_VALUE;
} else {
timeToIdle = tti;
timeToLive = ttl;
}
}
}
public class DefaultElementEvictionData implements ElementEvictionData {
private long creationTime;
private long lastAccessTime;
}
public class Cache implements InternalEhcache, StoreListener {
private void applyDefaultsToElementWithoutLifespanSet(Element element) {
if (!element.isLifespanSet()) {
element.setLifespanDefaults(TimeUtil.convertTimeToInt(configuration.getTimeToIdleSeconds()),
TimeUtil.convertTimeToInt(configuration.getTimeToLiveSeconds()),
configuration.isEternal());
}
}
}
EHCache中的Store設計
Store是EHCache中用于存儲、管理所有Element的倉庫,它抽象出了所有對Element在內存中以及磁盤中的操作。基本的它可以向一個Store中添加Element(put、putAll、putWithWriter、putIfAbsent)、從一個Store獲取一個或一些Element(get、getQuiet、getAll、getAllQuiet)、獲取一個Store中所有key(getKeys)、從一個Store中移除一個或一些Element(remove、removeElement、removeAll、removeWithWriter)、替換一個Store中已存儲的Element(replace)、pin或unpin一個Element(unpinAll、isPinned、setPinned)、添加或刪除StoreListener(addStoreListener、removeStoreListener)、獲取一個Store的Element數量(getSize、getInMemorySize、getOffHeapSize、getOnDiskSize、getTerracottaClusteredSize)、獲取一個Store的Element以Byte為單位的大小(getInMemorySizeInBytes、getOffHeapSizeInBytes、getOnDiskSizeInBytes)、判斷一個key的存在性(containsKey、containsKeyOnDisk、containsKeyOffHeap、containsKeyInMemory)、query操作(setAttributeExtractors、executeQuery、getSearchAttribute)、cluster相關操作(isCacheCoherent、isClusterCoherent、isNodeCoherent、setNodeCoherent、waitUtilClusterCoherent)、其他操作(dispose、getStatus、getMBean、hasAbortedSizeOf、expireElements、flush、bufferFull、getInMemoryEvictionPolicy、setInMemoryEvictionPolicy、getInternalContext、calculateSize)。
所謂Cache,就是將部分常用數據緩存在內存中,從而提升程序的效率,然而內存大小畢竟有限,因而有時候也需要有磁盤加以輔助,因而在EHCache中真正的Store實現就兩種(不考慮分布式緩存的情況下):存儲在內存中的MemoryStore和存儲在磁盤中的DiskStore,而所有其他Store都是給予這兩個Store的基礎上來擴展Store的功能,如因為內存大小的限制,有些時候需要將內存中的暫時不用的Element寫入到磁盤中,以騰出空間給其他更常用的Element,此時就需要MemoryStore和DiskStore共同來完成,這就是FrontCacheTier做的事情,所有可以結合FrontEndCacheTier一起使用的Store都要實現TierableStore接口(DiskStore、MemoryStore、NullStore);對于可控制Store占用空間大小做限制的Store還可以實現PoolableStore(DiskStore、MemoryStore);對于具有Terracotta特性的Store還實現了TerracottaStore接口(TransactionStore等)。
EHCache中Store的設計類結構圖如下:

AbstractStore
幾乎所有的Store實現都繼承自AbstractStore,它實現了Query、Cluster等相關的接口,但沒有涉及Element的管理,而且這部分現在也不了解,不詳述。
MemoryStore和NotifyingMemoryStore
MemoryStore是EHCache中存儲在內存中的Element的倉庫。它使用SelectableConcurrentHashMap作為內部的存儲結構,該類實現參考ConcurrentHashMap,只是它加入了pinned、evict等邏輯,不詳述(注:它的setPinned方法中,對不存在的key,會使用一個DUMMY_PINNED_ELEMENT來創建一個節點,并將它添加到HashEntry的鏈中,這時為什么?竊以為這個應該是為了在以后這個key添加進來后,當前的pinned設置可以對它有影響,因為MemoryStore中并沒有包含所有的Element,還有一部分Element是在DiskStore中)。而MemoryStore中的基本實現都代理給SelectableConcurrentHashMap,里面的其他細節在之前的文章中也有說明,不再贅述。
而NotifyingMemoryStore繼承自MemoryStore,它在Element evict和exipre時會調用注冊的CacheEventListener。
DiskStore
DiskStore依然采用ConcurrentHashMap的實現思想,因而這部分邏輯不贅述。對DiskStore,當一個Element添加進來后,需要將其寫入到磁盤中,這是接下來關注的重點。在DiskStore中,一個Element不再以Element本身而存在,而是以DiskSubstitute的實例而存在,DiskSubstitute有兩個子類:PlaceHolder和DiskMarker,當一個Element初始被添加到DiskStore中時,它是以PlaceHolder的形式存在,當這個PlaceHolder被寫入到磁盤中時,它會轉換成DiskMarker。
public abstract static class DiskSubstitute {
protected transient volatile long onHeapSize;
@IgnoreSizeOf
private transient volatile DiskStorageFactory factory;
DiskSubstitute(DiskStorageFactory factory) {
this.factory = factory;
}
abstract Object getKey();
abstract long getHitCount();
abstract long getExpirationTime();
abstract void installed();
public final DiskStorageFactory getFactory() {
return factory;
}
}
final class Placeholder extends DiskSubstitute {
@IgnoreSizeOf
private final Object key;
private final Element element;
private volatile boolean failedToFlush;
Placeholder(Element element) {
super(DiskStorageFactory.this);
this.key = element.getObjectKey();
this.element = element;
}
@Override
public void installed() {
DiskStorageFactory.this.schedule(new PersistentDiskWriteTask(this));
}
}
public static class DiskMarker extends DiskSubstitute implements Serializable {
@IgnoreSizeOf
private final Object key;
private final long position;
private final int size;
private volatile long hitCount;
private volatile long expiry;
DiskMarker(DiskStorageFactory factory, long position, int size, Element element) {
super(factory);
this.position = position;
this.size = size;
this.key = element.getObjectKey();
this.hitCount = element.getHitCount();
this.expiry = element.getExpirationTime();
}
@Override
public void installed() {
//no-op
}
void hit(Element e) {
hitCount++;
expiry = e.getExpirationTime();
}
}
當向DiskStore添加一個Element時,它會先創建一個PlaceHolder,并將該PlaceHolder添加到DiskStore中,并在添加完成后調用PlaceHolder的installed()方法,該方法會使用DiskStorageFactory schedule一個PersistentDiskWriteTask,將該PlaceHolder寫入到磁盤(在DiskStorageFactory有一個DiskWriter線程會在一定的時候執行該Task)生成一個DiskMarker,釋放PlaceHolder占用的內存。在從DiskStore移除一個Element時,它會先讀取磁盤中的數據,將其解析成Element,然后釋放這個Element占用的磁盤空間,并返回這個被移除的Element。在從DiskStore讀取一個Element時,它需要找到DiskStore中的DiskSubstitute,對DiskMarker讀取磁盤中的數據,解析成Element,然后返回。
FrontEndCacheTier
上述的MemoryStore和DiskStore,他們是各自獨立的,然而Cache的一個重要特點是可以將部分內存中的數據evict出到磁盤,因為內存畢竟是有限的,所以需要有另一個Store可以將MemoryStore和DiskStore聯系起來,這就是FrontEndCacheTier做的事情。FrontEndCacheTier有兩個子類:DiskBackedMemoryStore和MemoryOnlyStore,這兩個類的名字已經能很好的說明他們的用途了,DiskBackedMemoryStore可以將部分Element先evict出到磁盤,它也支持把磁盤文件作為persistent介質,在下一次讀取時可以直接從磁盤中的文件直接讀取并重新構建原來的緩存;而MemoryOnlyStore則只支持將Element存儲在內存中。FrontEndCacheTier有兩個Store屬性:cache和authority,它將基本上所有的操作都直接同時代理給這兩個Store,其中把authority作為主的存儲Store,而將cache作為緩存的Store。在DiskBackedMemoryStore中,authority是DiskStore,而cache是MemoryStore,即DiskBackedMemoryStore將DiskStore作為主的存儲Store,這剛開始讓我很驚訝,不過仔細想想也是合理的,因為畢竟這里的Disk是作為persistent介質的;在MemoryOnlyStore中,authority是MemoryStore,而cache是NullStore。
FrontEndCacheTier在實現get方法時,添加了faults屬性的ConcurrentHashMap,它是用于多個線程在同時讀取同一key的Element時避免多次讀取,每次之前將key和一個Fault新實例添加到faults中,這樣第二個線程發現已經有另一個線程在讀這個Element了,它就可以等待第一個線程讀完直接拿第一個線程讀取的結果即可,以提升性能。
所有作為FrontEndCacheTier的內部Store都必須實現TierableStore接口,其中fill、removeIfNotPinned、isTierPinned、getPresentPinnedKeys為cache store準備,而removeNoReturn、isPersistent為authority store準備。
public interface TierableStore extends Store {
void fill(Element e);
boolean removeIfNotPinned(Object key);
void removeNoReturn(Object key);
boolean isTierPinned();
Set getPresentPinnedKeys();
boolean isPersistent();
}
LruMemoryStore和LegacyStoreWrapper
這兩個Store只是為了兼容而存在,其中LruMemoryStore使用LinkedHashMap作為其存儲結構,他只支持一種Evict算法:LRU,這個Store的名字也因此而來,其他功能它類似MemoryStore,而LegacyStoreWrapper則類似FrontEndCacheTier。這兩個Store的代碼比較簡單,而且他們也不應該再被使用,因而不細究。
TerraccottaStore
對所有實現這個接口的Store都還不了解,看以后有沒有時間回來了。。。。
posted on 2013-11-03 22:05
DLevin 閱讀(6367)
評論(1) 編輯 收藏 所屬分類:
EHCache