1. 简介
正如上篇文章所说,Ehcache采用了多级缓存堆内、堆外、磁盘,每级缓存容量递增,最底层被称为Authoritative Tier,其余的缓存层被称为Caching Tier。Authoritative Tier层数据是最全的,其余层的数据都是该层的数据子集,只是临时存储数据。
当Caching Tier容量不足时,Ehcache将触发淘汰机制,本文将详细介绍淘汰机制的原理。
2. 代码分析
2.1 org.ehcache.impl.internal.concurrent.ConcurrentHashMap
Ehcache自己实现了一个ConcurrentHashMap,与JVM原生的ConcurrentHashMap稍有不同,它实现了org.ehcache.impl.internal.concurrent.EvictingConcurrentMap接口。
get时不加锁,put时使用内置锁,锁的粒度在数组中元素的维度。
2.2.1 getEvictionCandidate 方法
本方法是获取淘汰候选者的核心方法
public Entry<K, V> getEvictionCandidate(Random rndm, int size, Comparator<? super V> prioritizer, EvictionAdvisor<? super K, ? super V> evictionAdvisor) {
ConcurrentHashMap.Node[] tab = this.table;
if(tab != null && size != 0) {
Object maxKey = null;
Object maxValue = null;
int n = tab.length;
int start = rndm.nextInt(n);
ConcurrentHashMap.Traverser t = new ConcurrentHashMap.Traverser(tab, n, start, n);
ConcurrentHashMap.Node p;
Object key;
Object val;
do {
do {
if((p = t.advance()) == null) {
return this.getEvictionCandidateWrap(tab, start, size, maxKey, maxValue, prioritizer, evictionAdvisor);
}
key = p.key;
val = p.val;
} while(evictionAdvisor.adviseAgainstEviction(key, val));
if(maxKey == null || prioritizer.compare(val, maxValue) > 0) {
maxKey = key;
maxValue = val;
}
--size;
} while(size != 0);
int terminalIndex = t.index;
while((p = t.advance()) != null && t.index == terminalIndex) {
key = p.key;
val = p.val;
if(!evictionAdvisor.adviseAgainstEviction(key, val) && prioritizer.compare(val, maxValue) > 0) {
maxKey = key;
maxValue = val;
}
}
return new ConcurrentHashMap.MapEntry(maxKey, maxValue, this);
} else {
return null;
}
}
prioritizer实现了Comparator接口,外部传入的prioritizer默认实现如下,即按最后访问时间淘汰。
private static final Comparator<ValueHolder<?>> EVICTION_PRIORITIZER = (t, u) -> {
if (t instanceof Fault) {
return -1;
} else if (u instanceof Fault) {
return 1;
} else {
return Long.signum(u.lastAccessTime() - t.lastAccessTime());
}
};
2.2 org.ehcache.impl.internal.store.tiering.TieredStore
TieredStore类是分层缓存的核心类
2.2.1 put
@Override
public PutStatus put(final K key, final V value) throws StoreAccessException {
try {
return authoritativeTier.put(key, value);
} finally {
cachingTier().invalidate(key);
}
}
将key和value写入Authoritative Tier层,并在Caching Tier层失效key。
2.2.2 get
@Override
public ValueHolder<V> get(final K key) throws StoreAccessException {
try {
return cachingTier().getOrComputeIfAbsent(key, keyParam -> {
try {
return authoritativeTier.getAndFault(keyParam);
} catch (StoreAccessException cae) {
throw new StorePassThroughException(cae);
}
});
} catch (StoreAccessException ce) {
return handleStoreAccessException(ce);
}
}
先从Caching Tier层查找key,如果没有则访问Authoritative Tier层,更多逻辑写在CompoundCachingTier的get方法中。
2.3 org.ehcache.impl.internal.store.tiering.CompoundCachingTier
2.3.1 getOrComputeIfAbsent方法
@Override
public Store.ValueHolder<V> getOrComputeIfAbsent(K key, final Function<K, Store.ValueHolder<V>> source) throws StoreAccessException {
try {
return higher.getOrComputeIfAbsent(key, keyParam -> {
try {
Store.ValueHolder<V> valueHolder = lower.getAndRemove(keyParam);
if (valueHolder != null) {
return valueHolder;
}
return source.apply(keyParam);
} catch (StoreAccessException cae) {
throw new ComputationException(cae);
}
});
} catch (ComputationException ce) {
throw ce.getStoreAccessException();
}
}
此处需要调用到堆内、堆外层的getOrComputeIfAbsent逻辑
2.4 org.ehcache.impl.internal.store.heap.OnHeapStore
2.4.1 getOrComputeIfAbsent方法
public ValueHolder<V> getOrComputeIfAbsent(K key, Function<K, ValueHolder<V>> source) throws StoreAccessException {
try {
getOrComputeIfAbsentObserver.begin();
Backend<K, V> backEnd = map;
// 从backEnd map中查找
OnHeapValueHolder<V> cachedValue = backEnd.get(key);
long now = timeSource.getTimeMillis();
if (cachedValue == null) {
Fault<V> fault = new Fault<>(() -> source.apply(key));
cachedValue = backEnd.putIfAbsent(key, fault);
if (cachedValue == null) {
return resolveFault(key, backEnd, now, fault);
}
}
// 如果key-value存在,则判断是否过期
// 如果过期就删除此key-value键值对
if (!(cachedValue instanceof Fault)) {
if (cachedValue.isExpired(now)) {
expireMappingUnderLock(key, cachedValue);
Fault<V> fault = new Fault<>(() -> source.apply(key));
cachedValue = backEnd.putIfAbsent(key, fault);
if (cachedValue == null) {
return resolveFault(key, backEnd, now, fault);
}
}
else {
strategy.setAccessAndExpiryTimeWhenCallerOutsideLock(key, cachedValue, now);
}
}
getOrComputeIfAbsentObserver.end(CachingTierOperationOutcomes.GetOrComputeIfAbsentOutcome.HIT);
return getValue(cachedValue);
} catch (RuntimeException re) {
throw handleException(re);
}
}
2.4.2 enforceCapacity方法
protected void enforceCapacity() {
StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
try {
for (int attempts = 0, evicted = 0; attempts < ATTEMPT_RATIO && evicted < EVICTION_RATIO
&& capacity < map.naturalSize(); attempts++) {
if (evict(eventSink)) {
evicted++;
}
}
storeEventDispatcher.releaseEventSink(eventSink);
} catch (RuntimeException re){
storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
throw re;
}
}
enforceCapacity方法负责检查容量,如果超过阈值则淘汰部分key-value键值对。调用evict方法执行淘汰逻辑。
2.4.3 evict方法
boolean evict(StoreEventSink<K, V> eventSink) {
evictionObserver.begin();
Random random = new Random();
@SuppressWarnings("unchecked")
Map.Entry<K, OnHeapValueHolder<V>> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR);
if (candidate == null) {
candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice());
}
if (candidate == null) {
return false;
} else {
Map.Entry<K, OnHeapValueHolder<V>> evictionCandidate = candidate;
AtomicBoolean removed = new AtomicBoolean(false);
map.computeIfPresent(evictionCandidate.getKey(), (mappedKey, mappedValue) -> {
if (mappedValue.equals(evictionCandidate.getValue())) {
removed.set(true);
if (!(evictionCandidate.getValue() instanceof Fault)) {
eventSink.evicted(evictionCandidate.getKey(), evictionCandidate.getValue());
invalidationListener.onInvalidation(mappedKey, evictionCandidate.getValue());
}
updateUsageInBytesIfRequired(-mappedValue.size());
return null;
}
return mappedValue;
});
if (removed.get()) {
evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.SUCCESS);
return true;
} else {
evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.FAILURE);
return false;
}
}
}
实例化一个Random对象,传入ConcurrentHashMap,并调用getEvictionCandidate方法获取淘汰候选者。
3. 总结
通过上面的源码分析,我们可以理解Ehcache的缓存淘汰机制,并了解Ehcache自定义的ConcurrentHashmap类。
Ehcache源码中大量使用了Observer观察者模式,也值得我们学习。