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觀察者模式,也值得我們學習。

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