Google 出的 Guava 是個(gè)什么鬼?
前言
Google 出的?Guava?是 Java 核心增強(qiáng)的庫(kù),應(yīng)用非常廣泛。
我平時(shí)用的也挺頻繁,這次就借助日常使用的 Cache 組件來(lái)看看 Google 大牛們是如何設(shè)計(jì)的。
緩存
本次主要討論緩存。
緩存在日常開(kāi)發(fā)中舉足輕重,如果你的應(yīng)用對(duì)某類數(shù)據(jù)有著較高的讀取頻次,并且改動(dòng)較小時(shí)那就非常適合利用緩存來(lái)提高性能。
緩存之所以可以提高性能是因?yàn)樗淖x取效率很高,就像是 CPU 的?L1、L2、L3?緩存一樣,級(jí)別越高相應(yīng)的讀取速度也會(huì)越快。
但也不是什么好處都占,讀取速度快了但是它的內(nèi)存更小資源更寶貴,所以我們應(yīng)當(dāng)緩存真正需要的數(shù)據(jù)。
其實(shí)也就是典型的空間換時(shí)間。
下面談?wù)?Java 中所用到的緩存。
JVM 緩存
首先是 JVM 緩存,也可以認(rèn)為是堆緩存。
其實(shí)就是創(chuàng)建一些全局變量,如?Map、List?之類的容器用于存放數(shù)據(jù)。
這樣的優(yōu)勢(shì)是使用簡(jiǎn)單但是也有以下問(wèn)題:
只能顯式的寫入,清除數(shù)據(jù)。
不能按照一定的規(guī)則淘汰數(shù)據(jù),如?LRU,LFU,F(xiàn)IFO?等。
清除數(shù)據(jù)時(shí)的回調(diào)通知。
其他一些定制功能等。
Ehcache、Guava Cache
所以出現(xiàn)了一些專門用作 JVM 緩存的開(kāi)源工具出現(xiàn)了,如本文提到的 Guava Cache。
它具有上文 JVM 緩存不具有的功能,如自動(dòng)清除數(shù)據(jù)、多種清除算法、清除回調(diào)等。
但也正因?yàn)橛辛诉@些功能,這樣的緩存必然會(huì)多出許多東西需要額外維護(hù),自然也就增加了系統(tǒng)的消耗。
分布式緩存
剛才提到的兩種緩存其實(shí)都是堆內(nèi)緩存,只能在單個(gè)節(jié)點(diǎn)中使用,這樣在分布式場(chǎng)景下就招架不住了。
于是也有了一些緩存中間件,如 Redis、Memcached,在分布式環(huán)境下可以共享內(nèi)存。
具體不在本次的討論范圍。
Guava Cache 示例
之所以想到 Guava 的 Cache,也是最近在做一個(gè)需求,大體如下:
從 Kafka 實(shí)時(shí)讀取出應(yīng)用系統(tǒng)的日志信息,該日志信息包含了應(yīng)用的健康狀況。 如果在時(shí)間窗口 N 內(nèi)發(fā)生了 X 次異常信息,相應(yīng)的我就需要作出反饋(報(bào)警、記錄日志等)。
對(duì)此 Guava 的 Cache 就非常適合,我利用了它的 N 個(gè)時(shí)間內(nèi)不寫入數(shù)據(jù)時(shí)緩存就清空的特點(diǎn),在每次讀取數(shù)據(jù)時(shí)判斷異常信息是否大于 X 即可。
偽代碼如下:
首先是構(gòu)建了 LoadingCache 對(duì)象,在 N 分鐘內(nèi)不寫入數(shù)據(jù)時(shí)就回收緩存(當(dāng)通過(guò) Key 獲取不到緩存時(shí),默認(rèn)返回 0)。
然后在每次消費(fèi)時(shí)候調(diào)用?checkAlert()?方法進(jìn)行校驗(yàn),這樣就可以達(dá)到上文的需求。
我們來(lái)設(shè)想下 Guava 它是如何實(shí)現(xiàn)過(guò)期自動(dòng)清除數(shù)據(jù),并且是可以按照 LRU 這樣的方式清除的。
大膽假設(shè)下:
內(nèi)部通過(guò)一個(gè)隊(duì)列來(lái)維護(hù)緩存的順序,每次訪問(wèn)過(guò)的數(shù)據(jù)移動(dòng)到隊(duì)列頭部,并且額外開(kāi)啟一個(gè)線程來(lái)判斷數(shù)據(jù)是否過(guò)期,過(guò)期就刪掉。有點(diǎn)類似于我之前寫過(guò)的?動(dòng)手實(shí)現(xiàn)一個(gè) LRU cache
胡適說(shuō)過(guò):大膽假設(shè)小心論證
下面來(lái)看看 Guava 到底是怎么實(shí)現(xiàn)。
原理分析
看原理最好不過(guò)是跟代碼一步步走了:
示例代碼在這里:
https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java
為了能看出 Guava 是怎么刪除過(guò)期數(shù)據(jù)的在獲取緩存之前休眠了 5 秒鐘,達(dá)到了超時(shí)條件。
最終會(huì)發(fā)現(xiàn)在?com.google.common.cache.LocalCache?類的 2187 行比較關(guān)鍵。
再跟進(jìn)去之前第 2182 行會(huì)發(fā)現(xiàn)先要判斷 count 是否大于 0,這個(gè) count 保存的是當(dāng)前緩存的數(shù)量,并用 volatile 修飾保證了可見(jiàn)性。
更多關(guān)于 volatile 的相關(guān)信息可以查看?你應(yīng)該知道的 volatile 關(guān)鍵字
接著往下跟到:
2761 行,根據(jù)方法名稱可以看出是判斷當(dāng)前的 Entry 是否過(guò)期,該 entry 就是通過(guò) key 查詢到的。
這里就很明顯的看出是根據(jù)根據(jù)構(gòu)建時(shí)指定的過(guò)期方式來(lái)判斷當(dāng)前 key 是否過(guò)期了。
如果過(guò)期就往下走,嘗試進(jìn)行過(guò)期刪除(需要加鎖,后面會(huì)具體討論)。
到了這里也很清晰了:
獲取當(dāng)前緩存的總數(shù)量
自減一(前面獲取了鎖,所以線程安全)
刪除并將更新的總數(shù)賦值到 count。
其實(shí)大體上就是這個(gè)流程,Guava 并沒(méi)有按照之前猜想的另起一個(gè)線程來(lái)維護(hù)過(guò)期數(shù)據(jù)。
應(yīng)該是以下原因:
新起線程需要資源消耗。
維護(hù)過(guò)期數(shù)據(jù)還要獲取額外的鎖,增加了消耗。
而在查詢時(shí)候順帶做了這些事情,但是如果該緩存遲遲沒(méi)有訪問(wèn)也會(huì)存在數(shù)據(jù)不能被回收的情況,不過(guò)這對(duì)于一個(gè)高吞吐的應(yīng)用來(lái)說(shuō)也不是問(wèn)題。
總結(jié)
最后再來(lái)總結(jié)下 Guava 的 Cache。
其實(shí)在上文跟代碼時(shí)會(huì)發(fā)現(xiàn)通過(guò)一個(gè) key 定位數(shù)據(jù)時(shí)有以下代碼:
如果有看過(guò)?ConcurrentHashMap 的原理?應(yīng)該會(huì)想到這其實(shí)非常類似。
其實(shí) Guava Cache 為了滿足并發(fā)場(chǎng)景的使用,核心的數(shù)據(jù)結(jié)構(gòu)就是按照 ConcurrentHashMap 來(lái)的,這里也是一個(gè) key 定位到一個(gè)具體位置的過(guò)程。
先找到 Segment,再找具體的位置,等于是做了兩次 Hash 定位。
上文有一個(gè)假設(shè)是對(duì)的,它內(nèi)部會(huì)維護(hù)兩個(gè)隊(duì)列?accessQueue,writeQueue?用于記錄緩存順序,這樣才可以按照順序淘汰數(shù)據(jù)(類似于利用 LinkedHashMap 來(lái)做 LRU 緩存)。
同時(shí)從上文的構(gòu)建方式來(lái)看,它也是構(gòu)建者模式來(lái)創(chuàng)建對(duì)象的。
因?yàn)樽鳛橐粋€(gè)給開(kāi)發(fā)者使用的工具,需要有很多的自定義屬性,利用構(gòu)建則模式再合適不過(guò)了。
Guava 其實(shí)還有很多東西沒(méi)談到,比如它利用 GC 來(lái)回收內(nèi)存,移除數(shù)據(jù)時(shí)的回調(diào)通知等。之后再接著討論。
Java 緩存 JVM
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。