本地快取元件 Guava cache 詳解

快取分為本地快取和遠端快取。常見的遠端快取有Redis,MongoDB;本地快取一般使用map的方式儲存在本地記憶體中。一般我們在業務中操作快取,都會操作快取和資料來源兩部分。如:put資料時,先插入DB,再刪除原來的快取;ge資料時,先查快取,命中則返回,沒有命中時,需要查詢DB,再把查詢結果放入快取中 。如果訪問量大,我們還得兼顧本地快取的執行緒安全問題。必要的時候也要考慮快取的回收策略。

今天說的 Guava Cache 是google guava中的一個記憶體快取模組,用於將資料快取到JVM記憶體中。他很好的解決了上面提到的幾個問題:

很好的封裝了get、put操作,能夠整合資料來源 ;

執行緒安全的快取,與ConcurrentMap相似,但前者增加了更多的元素失效策略,後者只能顯示的移除元素;

Guava Cache提供了三種基本的快取回收方式:基於容量回收、定時回收和基於引用回收。定時回收有兩種:按照寫入時間,最早寫入的最先回收;按照訪問時間,最早訪問的最早回收;

監控快取載入/命中情況

Guava Cache的架構設計靈感ConcurrentHashMap,在簡單場景中可以透過HashMap實現簡單資料快取,但如果要實現快取隨時間改變、儲存的資料空間可控則快取工具還是很有必要的。Cache儲存的是鍵值對的集合,不同的是還需要處理快取過期、動態載入等演算法邏輯,需要額外資訊實現這些操作,對此根據面向物件的思想,還需要做方法與資料的關聯性封裝,主要實現的快取功能有:自動將節點載入至快取結構中,當快取的資料超過最大值時,使用LRU演算法替換;它具備根據節點上一次被訪問或寫入時間計算快取過期機制,快取的key被封裝在WeakReference引用中,快取的value被封裝在WeakReference或SoftReference引用中;還可以統計快取使用過程中的命中率、異常率和命中率等統計資料。

構建快取物件

先看一個示例,再來講解使用方式:

上面一段程式碼展示瞭如何使用Cache建立一個快取物件並使用它。

LoadingCache是Cache的子介面,相比較於Cache,當從LoadingCache中讀取一個指定key的記錄時,如果該記錄不存在,則LoadingCache可以自動執行載入資料到快取的操作。

在呼叫CacheBuilder的build方法時,必須傳遞一個CacheLoader型別的引數,CacheLoader的load方法需要我們提供實現。當呼叫LoadingCache的get方法時,如果快取不存在對應key的記錄,則CacheLoader中的load方法會被自動呼叫從外存載入資料,load方法的返回值會作為key對應的value儲存到LoadingCache中,並從get方法返回。

當然如果你不想指定重建策略,那麼你可以使用無參的build()方法,它將返回Cache型別的構建物件。

CacheBuilder 是Guava 提供的一個快速構建快取物件的工具類。CacheBuilder類採用builder設計模式,它的每個方法都返回CacheBuilder本身,直到build方法被呼叫。該類中提供了很多的引數設定選項,你可以設定cache的預設大小,併發數,存活時間,過期策略等等。

可選配置分析快取的併發級別

Guava提供了設定併發級別的api,使得快取支援併發的寫入和讀取。同 ConcurrentHashMap 類似Guava cache的併發也是透過分離鎖實現。在一般情況下,將併發級別設定為伺服器cpu核心數是一個比較不錯的選擇。

快取的初始容量設定

我們在構建快取時可以為快取設定一個合理大小初始容量,由於Guava的快取使用了分離鎖的機制,擴容的代價非常昂貴。所以合理的初始容量能夠減少快取容器的擴容次數。

設定最大儲存

Guava Cache可以在構建快取物件時指定快取所能夠儲存的最大記錄數量。當Cache中的記錄數量達到最大值後再呼叫put方法向其中新增物件,Guava會先從當前快取的物件記錄中選擇一條刪除掉,騰出空間後再將新的物件儲存到Cache中。

基於容量的清除(size-based eviction):

透過CacheBuilder。maximumSize(long)方法可以設定Cache的最大容量數,當快取數量達到或接近該最大值時,Cache將清除掉那些最近最少使用的快取;

基於權重的清除:

使用CacheBuilder。weigher(Weigher)指定一個權重函式,並且用CacheBuilder。maximumWeight(long)指定最大總重。比如每一項快取所佔據的記憶體空間大小都不一樣,可以看作它們有不同的“權重”(weights)。

快取清除策略1。 基於存活時間的清除

expireAfterWrite 寫快取後多久過期

expireAfterAccess 讀寫快取後多久過期

refreshAfterWrite 寫入資料後多久過期,只阻塞當前資料載入執行緒,其他執行緒返回舊值

這幾個策略時間可以單獨設定,也可以組合配置。

2。 上面提到的基於容量的清除3。 顯式清除

任何時候,你都可以顯式地清除快取項,而不是等到它被回收,Cache介面提供瞭如下API:

個別清除:Cache。invalidate(key)

批次清除:Cache。invalidateAll(keys)

清除所有快取項:Cache。invalidateAll()

4。 基於引用的清除(Reference-based Eviction)

在構建Cache例項過程中,透過設定使用弱引用的鍵、或弱引用的值、或軟引用的值,從而使JVM在GC時順帶實現快取的清除,不過一般不輕易使用這個特性。

CacheBuilder。weakKeys():使用弱引用儲存鍵。當鍵沒有其它(強或軟)引用時,快取項可以被垃圾回收。因為垃圾回收僅依賴恆等式,使用弱引用鍵的快取用而不是equals比較鍵。

CacheBuilder。weakValues():使用弱引用儲存值。當值沒有其它(強或軟)引用時,快取項可以被垃圾回收。因為垃圾回收僅依賴恆等式,使用弱引用值的快取用而不是equals比較值。

CacheBuilder。softValues():使用軟引用儲存值。軟引用只有在響應記憶體需要時,才按照全域性最近最少使用的順序回收。考慮到使用軟引用的效能影響,我們通常建議使用更有效能預測性的快取大小限定(見上文,基於容量回收)。使用軟引用值的快取同樣用==而不是equals比較值。

清理什麼時候發生

也許這個問題有點奇怪,如果設定的存活時間為一分鐘,難道不是一分鐘後這個key就會立即清除掉嗎?我們來分析一下如果要實現這個功能,那Cache中就必須存線上程來進行週期性地檢查、清除等工作,很多cache如redis、ehcache都是這樣實現的。

使用CacheBuilder構建的快取不會”自動”執行清理和回收工作,也不會在某個快取項過期後馬上清理,也沒有諸如此類的清理機制。相反,它會在寫操作時順帶做少量的維護工作,或者偶爾在讀操作時做——如果寫操作實在太少的話。

這樣做的原因在於:如果要自動地持續清理快取,就必須有一個執行緒,這個執行緒會和使用者操作競爭共享鎖。此外,某些環境下執行緒建立可能受限制,這樣CacheBuilder就不可用了。參考如下示例:

上面程式設定了快取過期時間為5S,每列印一次當前的size需要2S,列印了5次size之後寫入key 2,此時的size為1,說明在這個時候才把第一次應該過期的key 1給刪除。

給移除操作新增一個監聽器:

可以為Cache物件新增一個移除監聽器,這樣當有記錄被刪除時可以感知到這個事件。

但是要注意的是:

預設情況下,監聽器方法是在移除快取時同步呼叫的。因為快取的維護和請求響應通常是同時進行的,代價高昂的監聽器方法在同步模式下會拖慢正常的快取請求。在這種情況下,你可以使用

把監聽器裝飾為非同步操作。

自動載入

上面我們說過使用get方法的時候如果key不存在你可以使用指定方法去載入這個key。在Cache構建的時候透過指定CacheLoder的方式。如果你沒有指定,你也可以在get的時候顯式的呼叫call方法來設定key不存在的補救策略。

Cache的get方法有兩個引數,第一個引數是要從Cache中獲取記錄的key,第二個記錄是一個Callable物件。

當快取中已經存在key對應的記錄時,get方法直接返回key對應的記錄。如果快取中不包含key對應的記錄,Guava會啟動一個執行緒執行Callable物件中的call方法,call方法的返回值會作為key對應的值被儲存到快取中,並且被get方法返回。

可以看到輸出結果:兩個執行緒都啟動,輸出thread1,thread2,接著又輸出了thread2,說明進入了thread2的call方法了,此時thread1正在阻塞,等待key被設定。然後thread1 得到了value是thread2,thread2的結果自然也是thread2。

這段程式碼中有兩個執行緒共享同一個Cache物件,兩個執行緒同時呼叫get方法獲取同一個key對應的記錄。由於key對應的記錄不存在,所以兩個執行緒都在get方法處阻塞。此處在call方法中呼叫Thread。sleep(1000)模擬程式從外存載入資料的時間消耗。

從結果中可以看出,雖然是兩個執行緒同時呼叫get方法,但只有一個get方法中的Callable會被執行(沒有打印出load2)。Guava可以保證當有多個執行緒同時訪問Cache中的一個key時,如果key對應的記錄不存在,Guava只會啟動一個執行緒執行get方法中Callable引數對應的任務載入資料存到快取。當載入完資料後,任何執行緒中的get方法都會獲取到key對應的值。

統計資訊

可以對Cache的命中率、載入資料時間等資訊進行統計。在構建Cache物件時,可以透過CacheBuilder的recordStats方法開啟統計資訊的開關。開關開啟後Cache會自動對快取的各種操作進行統計,呼叫Cache的stats方法可以檢視統計後的資訊。

喜歡,在看