ehcache3-源碼簡析三
阿新 • • 發佈:2017-08-24
func lap timeunit aps min ifreq ron 隨機數 unit
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 }View Code
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 }
ehcache3-源碼簡析三