天天看點

Guava 源碼分析(Cache 原理)

Google 出的 Guava 是 Java 核心增強的庫,應用非常廣泛。

我平時用的也挺頻繁,這次就借助日常使用的 Cache 元件來看看 Google 大牛們是如何設計的。

Guava 源碼分析(Cache 原理)
本次主要讨論緩存。

緩存在日常開發中舉足輕重,如果你的應用對某類資料有着較高的讀取頻次,并且改動較小時那就非常适合利用緩存來提高性能。

緩存之是以可以提高性能是因為它的讀取效率很高,就像是 CPU 的 <code>L1、L2、L3</code> 緩存一樣,級别越高相應的讀取速度也會越快。

但也不是什麼好處都占,讀取速度快了但是它的記憶體更小資源更寶貴,是以我們應當緩存真正需要的資料。

其實也就是典型的空間換時間。

下面談談 Java 中所用到的緩存。

首先是 JVM 緩存,也可以認為是堆緩存。

其實就是建立一些全局變量,如 <code>Map、List</code> 之類的容器用于存放資料。

這樣的優勢是使用簡單但是也有以下問題:

隻能顯式的寫入,清除資料。

不能按照一定的規則淘汰資料,如 <code>LRU,LFU,FIFO</code> 等。

清除資料時的回調通知。

其他一些定制功能等。

是以出現了一些專門用作 JVM 緩存的開源工具出現了,如本文提到的 Guava Cache。

它具有上文 JVM 緩存不具有的功能,如自動清除資料、多種清除算法、清除回調等。

但也正因為有了這些功能,這樣的緩存必然會多出許多東西需要額外維護,自然也就增加了系統的消耗。

剛才提到的兩種緩存其實都是堆内緩存,隻能在單個節點中使用,這樣在分布式場景下就招架不住了。

于是也有了一些緩存中間件,如 Redis、Memcached,在分布式環境下可以共享記憶體。

具體不在本次的讨論範圍。

之是以想到 Guava 的 Cache,也是最近在做一個需求,大體如下:

從 Kafka 實時讀取出應用系統的日志資訊,該日志資訊包含了應用的健康狀況。 如果在時間視窗 N 内發生了 X 次異常資訊,相應的我就需要作出回報(報警、記錄日志等)。

對此 Guava 的 Cache 就非常适合,我利用了它的 N 個時間内不寫入資料時緩存就清空的特點,在每次讀取資料時判斷異常資訊是否大于 X 即可。

僞代碼如下:

首先是建構了 LoadingCache 對象,在 N 分鐘内不寫入資料時就回收緩存(當通過 Key 擷取不到緩存時,預設傳回 0)。

然後在每次消費時候調用 <code>checkAlert()</code> 方法進行校驗,這樣就可以達到上文的需求。

我們來設想下 Guava 它是如何實作過期自動清除資料,并且是可以按照 LRU 這樣的方式清除的。

大膽假設下:

内部通過一個隊列來維護緩存的順序,每次通路過的資料移動到隊列頭部,并且額外開啟一個線程來判斷資料是否過期,過期就删掉。有點類似于我之前寫過的 動手實作一個 LRU cache

胡适說過:大膽假設小心論證

下面來看看 Guava 到底是怎麼實作。

看原理最好不過是跟代碼一步步走了:

示例代碼在這裡:

https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java

Guava 源碼分析(Cache 原理)

為了能看出 Guava 是怎麼删除過期資料的在擷取緩存之前休眠了 5 秒鐘,達到了逾時條件。

Guava 源碼分析(Cache 原理)

最終會發現在 <code>com.google.common.cache.LocalCache</code> 類的 2187 行比較關鍵。

再跟進去之前第 2182 行會發現先要判斷 count 是否大于 0,這個 count 儲存的是目前緩存的數量,并用 volatile 修飾保證了可見性。

更多關于 volatile 的相關資訊可以檢視 你應該知道的 volatile 關鍵字

接着往下跟到:

Guava 源碼分析(Cache 原理)

2761 行,根據方法名稱可以看出是判斷目前的 Entry 是否過期,該 entry 就是通過 key 查詢到的。

這裡就很明顯的看出是根據根據建構時指定的過期方式來判斷目前 key 是否過期了。

Guava 源碼分析(Cache 原理)

如果過期就往下走,嘗試進行過期删除(需要加鎖,後面會具體讨論)。

Guava 源碼分析(Cache 原理)

到了這裡也很清晰了:

擷取目前緩存的總數量

自減一(前面擷取了鎖,是以線程安全)

删除并将更新的總數指派到 count。

其實大體上就是這個流程,Guava 并沒有按照之前猜想的另起一個線程來維護過期資料。

應該是以下原因:

新起線程需要資源消耗。

維護過期資料還要擷取額外的鎖,增加了消耗。

而在查詢時候順帶做了這些事情,但是如果該緩存遲遲沒有通路也會存在資料不能被回收的情況,不過這對于一個高吞吐的應用來說也不是問題。

最後再來總結下 Guava 的 Cache。

其實在上文跟代碼時會發現通過一個 key 定位資料時有以下代碼:

Guava 源碼分析(Cache 原理)

如果有看過 ConcurrentHashMap 的原理 應該會想到這其實非常類似。

其實 Guava Cache 為了滿足并發場景的使用,核心的資料結構就是按照 ConcurrentHashMap 來的,這裡也是一個 key 定位到一個具體位置的過程。

先找到 Segment,再找具體的位置,等于是做了兩次 Hash 定位。

上文有一個假設是對的,它内部會維護兩個隊列 <code>accessQueue,writeQueue</code> 用于記錄緩存順序,這樣才可以按照順序淘汰資料(類似于利用 LinkedHashMap 來做 LRU 緩存)。

同時從上文的建構方式來看,它也是建構者模式來建立對象的。

因為作為一個給開發者使用的工具,需要有很多的自定義屬性,利用建構則模式再合适不過了。

Guava 其實還有很多東西沒談到,比如它利用 GC 來回收記憶體,移除資料時的回調通知等。之後再接着讨論。

最後插播個小廣告:

Java-Interview 截止目前将近 8K star。

這次定個小目标:争取沖擊 <code>1W star</code>。

感謝各位老鐵的支援與點贊。

歡迎關注公衆号一起交流:

作者:

crossoverJie

出處:

https://crossoverjie.top

Guava 源碼分析(Cache 原理)

歡迎關注部落客公衆号與我交流。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,

如有問題, 可郵件(crossoverJie#gmail.com)咨詢。

上一篇: XSD 複合元素
下一篇: XSD 空元素