写了那么多,终于到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都还不了解,看以后有没有时间回来了。。。。