ehcache3的evict策略是怎样的呢?从put操作可以一窥,这里以单层heap cache为例。
ehcache3的evict策略不可设置,只能通过eviction-advisor建议evict,但这种建议得不到保证且低效。ehcache3的evict策略其实是一种基于样本的LRU算法,即在全量数据中采集一定数量样本(默认为8),在样本集中选取lastAccessTime最小的进行evict。
1 //put操作先存入元素,然后判断是否进行evict,有删减 2 public PutStatus put(final K key, final V value) throws StoreAccessException { 3 4 checkKey(key); 5 checkValue(value); 6 final long now = timeSource.getTimeMillis(); 7 8 //map.compute会进入ConcurrentHashMap遍历元素并计算value 9 map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() { 10 @Override 11 public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) { 12 13 if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) { 14 updateUsageInBytesIfRequired(- mappedValue.size()); 15 mappedValue = null; 16 } 17 18 if (mappedValue == null) { 19 OnHeapValueHolder<V> newValue = newCreateValueHolder(key, value, now, eventSink); 20 if (newValue != null) { 21 updateUsageInBytesIfRequired(newValue.size()); 22 statOutcome.set(StoreOperationOutcomes.PutOutcome.PUT); 23 } 24 return newValue; 25 } else { 26 OnHeapValueHolder<V> newValue = newUpdateValueHolder(key, mappedValue, value, now, eventSink); 27 if (newValue != null) { 28 updateUsageInBytesIfRequired(newValue.size() - mappedValue.size()); 29 } else { 30 updateUsageInBytesIfRequired(- mappedValue.size()); 31 } 32 statOutcome.set(StoreOperationOutcomes.PutOutcome.REPLACED); 33 return newValue; 34 } 35 } 36 }); 37 38 //enforceCapacity会判断是否进行evict操作。 39 enforceCapacity(); 40 41 }
ConcurrentHashMap的compute操作
1 public V compute(K key, 2 BiFunction<? super K, ? super V, ? extends V> remappingFunction) { 3 if (key == null || remappingFunction == null) 4 throw new NullPointerException(); 5 int h = spread(key.hashCode()); 6 V val = null; 7 int delta = 0; 8 int binCount = 0; 9 for (Node<K,V>[] tab = table;;) { 10 Node<K,V> f; int n, i, fh; 11 if (tab == null || (n = tab.length) == 0) 12 tab = initTable(); 13 else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { 14 Node<K,V> r = new ReservationNode<K,V>(); 15 synchronized (r) { 16 if (casTabAt(tab, i, null, r)) { 17 binCount = 1; 18 Node<K,V> node = null; 19 try { 20 //桶内无元素,计算key的value,如果value非null则添加该key-value(Node); 21 //如果value为null则什么也不做 22 if ((val = remappingFunction.apply(key, null)) != null) { 23 delta = 1; 24 node = new Node<K,V>(h, key, val, null); 25 } 26 } finally { 27 setTabAt(tab, i, node); 28 } 29 } 30 } 31 if (binCount != 0) 32 break; 33 } 34 else if ((fh = f.hash) == MOVED) 35 tab = helpTransfer(tab, f); 36 else { 37 synchronized (f) { 38 if (tabAt(tab, i) == f) { 39 if (fh >= 0) { 40 binCount = 1; 41 for (Node<K,V> e = f, pred = null;; ++binCount) { 42 K ek; 43 if (e.hash == h && 44 ((ek = e.key) == key || 45 (ek != null && key.equals(ek)))) { 46 //桶内找到与key对应的node,计算value 47 val = remappingFunction.apply(key, e.val); 48 //如果value非null,则使用计算后的value替换旧的value 49 if (val != null) 50 e.val = val; 51 //如果value为null,则删除该node 52 else { 53 delta = -1; 54 Node<K,V> en = e.next; 55 if (pred != null) 56 pred.next = en; 57 else 58 setTabAt(tab, i, en); 59 } 60 break; 61 } 62 pred = e; 63 //桶内没有找到key对应的node,根据key计算value, 64 //如果value非null则将该key-value加入桶内,如果value为null则什么都不做 65 if ((e = e.next) == null) { 66 val = remappingFunction.apply(key, null); 67 if (val != null) { 68 delta = 1; 69 pred.next = 70 new Node<K,V>(h, key, val, null); 71 } 72 break; 73 } 74 } 75 } 76 else if (f instanceof TreeBin) { 77 binCount = 1; 78 TreeBin<K,V> t = (TreeBin<K,V>)f; 79 TreeNode<K,V> r, p; 80 if ((r = t.root) != null) 81 p = r.findTreeNode(h, key, null); 82 else 83 p = null; 84 V pv = (p == null) ? null : p.val; 85 val = remappingFunction.apply(key, pv); 86 if (val != null) { 87 if (p != null) 88 p.val = val; 89 else { 90 delta = 1; 91 t.putTreeVal(h, key, val); 92 } 93 } 94 else if (p != null) { 95 delta = -1; 96 if (t.removeTreeNode(p)) 97 setTabAt(tab, i, untreeify(t.first)); 98 } 99 } 100 } 101 } 102 if (binCount != 0) { 103 if (binCount >= TREEIFY_THRESHOLD) 104 treeifyBin(tab, i); 105 break; 106 } 107 } 108 } 109 if (delta != 0) 110 addCount((long)delta, binCount); 111 return val; 112 }
1 protected void enforceCapacity() { 2 StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink(); 3 try { 4 //ATTEMPT_RATIO为4,即最多尝试evict4次,EVICTION_RATIO为2,即最多evict2个元素,capacity即我们设置的<heap unit="entries">x</heap>数,map.naturalSize()是当前已映射数 5 for (int attempts = 0, evicted = 0; attempts < ATTEMPT_RATIO && evicted < EVICTION_RATIO 6 && capacity < map.naturalSize(); attempts++) { 7 //如果evict成功,evicted++ 8 if (evict(eventSink)) { 9 evicted++; 10 } 11 } 12 storeEventDispatcher.releaseEventSink(eventSink); 13 } catch (RuntimeException re){ 14 storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re); 15 throw re; 16 } 17 }
1 boolean evict(final StoreEventSink<K, V> eventSink) { 2 evictionObserver.begin(); 3 //产生的随机数用于确定首个样本的index 4 final Random random = new Random(); 5 6 //第一轮采样。 7 //SAMPLE_SIZE为8,表示最少采样8个样本(如果样本不足就8个,采完就行), 8 //EVICTION_PRIORITIZER是一个Comparator,会比较node的lastAccessTime, 9 //EVICTION_ADVISOR即evict建议,可以自定义, 10 //第一轮采样会接收evict建议,如果第一轮年采样没evict的建议都是不evict, 11 //则进行第二轮采样,第二轮采样会忽略evict建议。 12 //注意,evictionAdvice在value存入时就已确定,即valueHolder中持有evictionAdvice(boolean) 13 Map.Entry<K, OnHeapValueHolder<V>> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR); 14 15 if (candidate == null) { 16 //第二轮采样 17 // 2nd attempt without any advisor 18 candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice()); 19 } 20 21 if (candidate == null) { 22 return false; 23 } else { 24 //根据key删除元素 25 final Map.Entry<K, OnHeapValueHolder<V>> evictionCandidate = candidate; 26 final AtomicBoolean removed = new AtomicBoolean(false); 27 map.computeIfPresent(evictionCandidate.getKey(), new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() { 28 @Override 29 public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) { 30 if (mappedValue.equals(evictionCandidate.getValue())) { 31 removed.set(true); 32 if (!(evictionCandidate.getValue() instanceof Fault)) { 33 eventSink.evicted(evictionCandidate.getKey(), evictionCandidate.getValue()); 34 invalidationListener.onInvalidation(mappedKey, evictionCandidate.getValue()); 35 } 36 updateUsageInBytesIfRequired(-mappedValue.size()); 37 return null;//return null会删除 38 } 39 return mappedValue; 40 } 41 }); 42 if (removed.get()) { 43 evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.SUCCESS); 44 return true; 45 } else { 46 evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.FAILURE); 47 return false; 48 } 49 } 50 }
1 public Entry<K, V> getEvictionCandidate(Random rndm, int size, Comparator<? super V> prioritizer, EvictionAdvisor<? super K, ? super V> evictionAdvisor) { 2 Node<K,V>[] tab = table; 3 if (tab == null || size == 0) { 4 return null; 5 } 6 7 K maxKey = null; 8 V maxValue = null; 9 10 int n = tab.length; 11 int start = rndm.nextInt(n); 12 13 Traverser<K, V> t = new Traverser<K, V>(tab, n, start, n); 14 //advance()可以得到下一个node(下一个node有两种情况,1桶内,直接通过next得到,2桶内的next==null,则遍历下一个桶) 15 for (Node<K, V> p; (p = t.advance()) != null;) { 16 K key = p.key; 17 V val = p.val; 18 //adviseAgainstEviction即不建议evict 19 if (!evictionAdvisor.adviseAgainstEviction(key, val)) { 20 //通过prioritizer(Comparator)的比较,得到lastAccessTime最小的 21 if (maxKey == null || prioritizer.compare(val, maxValue) > 0) { 22 maxKey = key; 23 maxValue = val; 24 } 25 //虽然已经样本数已经达到要求,但是仍然继续遍历当前桶内节点(t.index==terminalIndex) 26 if (--size == 0) { 27 for (int terminalIndex = t.index; (p = t.advance()) != null && t.index == terminalIndex; ) { 28 key = p.key; 29 val = p.val; 30 if (!evictionAdvisor.adviseAgainstEviction(key, val) && prioritizer.compare(val, maxValue) > 0) { 31 maxKey = key; 32 maxValue = val; 33 } 34 } 35 return new MapEntry<K, V>(maxKey, maxValue, this); 36 } 37 } 38 } 39 40 return getEvictionCandidateWrap(tab, start, size, maxKey, maxValue, prioritizer, evictionAdvisor); 41 }