Ehcache的缓存淘汰机制

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观察者模式,也值得我们学习。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章