Guava緩存器源碼分析——緩存統計器

Guava緩存器統計器實現:

全局統計器——
        1、CacheBuilder的靜態成員變量Supplier<StatsCounter> CACHE_STATS_COUNTER初始化時,重載的get方法,返回了一個SimpleStatsCounter實例。
        2、當緩存器開啓緩存統計時(recordStats),其成員變量statsCounterSupplier被賦值爲CACHE_STATS_COUNTER,若沒開啓則爲初始值NULL_STATS_COUNTER。
 3、在LocalCache的構造函數中,緩存器的全局統計器globalStatsCounter將從CacheBuilder中獲取:builder.getStatsCounterSupplier().get();
因此Guava緩存器的全局統計器實際上是SimpleStatsCounter類型。
       全局統計器只有在調用getAll, getAllPresent, getIfPresent, loadAll方法時纔會更新。

段統計器——

緩存器的每個段都有自己的統計器statsCounter,在LocalCache的構造函數中,通過createSegment方法創建所有的段,同時通過 builder.getStatsCounterSupplier().get()完成對段統計器的初始化,因此段統計器也是SimpleStatsCounter類型。

當用戶想查看緩存統計信息時,會調用stats方法,將全局統計器及每一個段統計器的信息綜合起來:
       public CacheStats stats() {
              SimpleStatsCounter aggregator = new SimpleStatsCounter();
              aggregator.incrementBy(localCache.globalStatsCounter);
              for (Segment<K, V> segment : localCache.segments) {
                    aggregator.incrementBy(segment.statsCounter);
              }
              return aggregator.snapshot();
        }
        最終通過snapshot方法,返回包含所有統計信息的CacheStats對象:
        public CacheStats snapshot() {
              return new CacheStats(
              hitCount.sum(),
              missCount.sum(),
              loadSuccessCount.sum(),
              loadExceptionCount.sum(),
              totalLoadTime.sum(),
              evictionCount.sum());
        }


SimpleStatsCounter中所有的LongAddable類型成員變量,都是通過LongAddables的create方法初始化,該靜態方法實際調用的爲 SUPPLIER.get()方法,返回一個LongAddable實例。

        在LongAddables 中有一段靜態代碼段,完成了對其成員變量Supplier<LongAddable> SUPPLIER的初始化,並重載了Supplier的get方法,該get方法返回一個LongAdder對象,如果初始化出現異常,則重新初始化SUPPLIER,但是重載的get方法中,返回一個PureJavaLongAddable對象。

1、PureJavaLongAddable繼承至AtomicLong,AtomicLong使用原子方法實現了對一個Long對象的增、減、更新等操作。 比如對於++運算符 AtomicLong 可以將它持有的 Long對象原子地遞增。 PureJavaLongAddable中的方法都通過AtomicLong來實現,以保證所有操作都能原子地完成,比如add方法實際調用的即爲AtomicLong.getAndAdd,該方法將當前值加上一個數,並返回原值:
       public final long getAndAdd(long delta) {
            while (true) {
                    long current = get();
                    long next = current + delta;
                    if (compareAndSet(current, next))
                            return current;
            }
        }

2、按Guava的說法,當多條線程在更新統計數據時,而不是細粒度同步控制的情況下,LongAdder比AtomicLong更好用。當更新爭用的頻率低時,兩個類效果比較相似,當爭用頻率很高時,LongAdder的吞吐率將會大大提升,但會消耗更大的空間。
        下面分析下LongAdder的add方法——
        其中cells和base都爲Striped64的成員變量,cells爲數組類型,base作爲一個保底值,當不發生爭用時更新它。
        public void add(long x) {
                Cell[] as; long b, v; HashCode hc; Cell a; int n;
                //當cells不爲空,或對base值更新失敗時,進入分支;
                if ((as = cells) != null || !casBase(b = base, b + x)) {
                        boolean uncontended = true;
                        //獲取hash值;
                        int h = (hc = threadHashCode.get()).code; 
                        //當cells爲空,或其長度小於1,或從cells中隨機取的cell爲空,或對隨機所取得cell更新失敗時(發生爭用),則進入分支,重新更新,若發生爭用,此時uncontended爲false,在重新更新時會使用到busy鎖;
                        if (as == null || (n = as.length) < 1 ||(a = as[(n - 1) & h]) == null || !(uncontended = a.cas(v = a.value, v + x)))
                                retryUpdate(x, hc, uncontended);
                }
        }

因此,LongAdder主要是通過Cell[] cells,將同步操作,轉嫁到隨機取得的cells元素上,從而使得爭用的概率大大降低,同時在發生爭用時,retryUpdate方法中還可能會對cells數據進行擴容,以降低爭用的發生:
        Cell[] rs = new Cell[n << 1];
        for (int i = 0; i < n; ++i)
                rs[i] = as[i];
        cells = rs;
雖然這確實會帶來一定的空間消耗,但緩存器本身對性能要求很高,以空間換時間是可以接受的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章