guava 堆緩存

緩存在應(yīng)用中是必不可少的斤程,經(jīng)常用的如redis角寸、memcache以及內(nèi)存緩存等。Guava是Google出的一個工具包忿墅,它里面的cache即是對本地內(nèi)存緩存的一種實(shí)現(xiàn)扁藕,支持多種緩存過期策略。?

Guava cache的緩存加載方式有兩種:

CacheLoader

Callable callback

具體兩種方式的介紹看官方文檔:http://ifeve.com/google-guava-cachesexplained/

接下來看看常見的一些使用方法疚脐。?

后面的示例實(shí)踐都是以CacheLoader方式加載緩存值亿柑。

1.簡單使用:定時過期

LoadingCache caches = CacheBuilder.newBuilder()

? ? ? ? ? ? ? ? .maximumSize(100)

? ? ? ? ? ? ? ? .expireAfterWrite(10, TimeUnit.MINUTES)

? ? ? ? ? ? ? ? .build(new CacheLoader() {

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? public Object load(String key) throws Exception {

? ? ? ? ? ? ? ? ? ? ? ? return generateValueByKey(key);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? });try {

? ? System.out.println(caches.get("key-zorro"));

} catch (ExecutionException e) {

? ? e.printStackTrace();

}


如代碼所示新建了名為caches的一個緩存對象,maximumSize定義了緩存的容量大小棍弄,當(dāng)緩存數(shù)量即將到達(dá)容量上線時望薄,則會進(jìn)行緩存回收,回收最近沒有使用或總體上很少使用的緩存項(xiàng)呼畸。需要注意的是在接近這個容量上限時就會發(fā)生痕支,所以在定義這個值的時候需要視情況適量地增大一點(diǎn)。?

另外通過expireAfterWrite這個方法定義了緩存的過期時間蛮原,寫入十分鐘之后過期卧须。?

在build方法里,傳入了一個CacheLoader對象,重寫了其中的load方法花嘶。當(dāng)獲取的緩存值不存在或已過期時笋籽,則會調(diào)用此load方法,進(jìn)行緩存值的計算椭员。?

這就是最簡單也是我們平常最常用的一種使用方法车海。定義了緩存大小、過期時間及緩存值生成方法隘击。

如果用其他的緩存方式侍芝,如redis,我們知道上面這種“如果有緩存則返回闸度;否則運(yùn)算竭贩、緩存、然后返回”的緩存模式是有很大弊端的莺禁。當(dāng)高并發(fā)條件下同時進(jìn)行g(shù)et操作留量,而此時緩存值已過期時,會導(dǎo)致大量線程都調(diào)用生成緩存值的方法哟冬,比如從數(shù)據(jù)庫讀取楼熄。這時候就容易造成數(shù)據(jù)庫雪崩。這也就是我們常說的“緩存穿透”浩峡。?

而Guava cache則對此種情況有一定控制可岂。當(dāng)大量線程用相同的key獲取緩存值時,只會有一個線程進(jìn)入load方法翰灾,而其他線程則等待缕粹,直到緩存值被生成。這樣也就避免了緩存穿透的危險纸淮。

2.進(jìn)階使用:定時刷新

如上的使用方法平斩,雖然不會有緩存穿透的情況,但是每當(dāng)某個緩存值過期時咽块,老是會導(dǎo)致大量的請求線程被阻塞绘面。而Guava則提供了另一種緩存策略,緩存值定時刷新:更新線程調(diào)用load方法更新該緩存侈沪,其他請求線程返回該緩存的舊值揭璃。這樣對于某個key的緩存來說,只會有一個線程被阻塞亭罪,用來生成緩存值瘦馍,而其他的線程都返回舊的緩存值,不會被阻塞皆撩。?

這里就需要用到Guava cache的refreshAfterWrite方法扣墩。如下所示:

LoadingCache caches = CacheBuilder.newBuilder()

? ? ? ? ? ? ? ? .maximumSize(100)

? ? ? ? ? ? ? ? .refreshAfterWrite(10, TimeUnit.MINUTES)

? ? ? ? ? ? ? ? .build(new CacheLoader() {

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ? public Object load(String key) throws Exception {

? ? ? ? ? ? ? ? ? ? ? ? return generateValueByKey(key);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? });try {

? ? System.out.println(caches.get("key-zorro"));

} catch (ExecutionException e) {

? ? e.printStackTrace();

}

如代碼所示哲银,每隔十分鐘緩存值則會被刷新扛吞。

此外需要注意一個點(diǎn)呻惕,這里的定時并不是真正意義上的定時。Guava cache的刷新需要依靠用戶請求線程滥比,讓該線程去進(jìn)行l(wèi)oad方法的調(diào)用亚脆,所以如果一直沒有用戶嘗試獲取該緩存值,則該緩存也并不會刷新盲泛。

3.進(jìn)階使用:異步刷新

如2中的使用方法濒持,解決了同一個key的緩存過期時會讓多個線程阻塞的問題,只會讓用來執(zhí)行刷新緩存操作的一個用戶線程會被阻塞寺滚。由此可以想到另一個問題柑营,當(dāng)緩存的key很多時,高并發(fā)條件下大量線程同時獲取不同key對應(yīng)的緩存村视,此時依然會造成大量線程阻塞官套,并且給數(shù)據(jù)庫帶來很大壓力。這個問題的解決辦法就是將刷新緩存值的任務(wù)交給后臺線程蚁孔,所有的用戶請求線程均返回舊的緩存值奶赔,這樣就不會有用戶線程被阻塞了。?

詳細(xì)做法如下:

ListeningExecutorService backgroundRefreshPools =

? ? ? ? ? ? ? ? MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(20));

? ? ? ? LoadingCache caches = CacheBuilder.newBuilder()

? ? ? ? ? ? ? ? .maximumSize(100)

? ? ? ? ? ? ? ? .refreshAfterWrite(10, TimeUnit.MINUTES)

? ? ? ? ? ? ? ? .build(new CacheLoader() {

? ? ? ? ? ? ? ? ? ? @Override? ? ? ? ? ? ? ? ? ? public Object load(String key) throws Exception {

? ? ? ? ? ? ? ? ? ? ? ? return generateValueByKey(key);

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? @Override? ? ? ? ? ? ? ? ? ? public ListenableFuture reload(String key,

? ? ? ? ? ? ? ? ? ? ? ? ? ? Object oldValue) throws Exception {

? ? ? ? ? ? ? ? ? ? ? ? return backgroundRefreshPools.submit(new Callable() {

? ? ? ? ? ? ? ? ? ? ? ? ? ? @Override? ? ? ? ? ? ? ? ? ? ? ? ? ? public Object call() throws Exception {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return generateValueByKey(key);

? ? ? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? });

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? });try {

? ? System.out.println(caches.get("key-zorro"));

} catch (ExecutionException e) {

? ? e.printStackTrace();

}

在上面的代碼中杠氢,我們新建了一個線程池站刑,用來執(zhí)行緩存刷新任務(wù)。并且重寫了CacheLoader的reload方法鼻百,在該方法中建立緩存刷新的任務(wù)并提交到線程池绞旅。?

注意此時緩存的刷新依然需要靠用戶線程來驅(qū)動,只不過和2不同之處在于該用戶線程觸發(fā)刷新操作之后温艇,會立馬返回舊的緩存值因悲。

TIPS

可以看到防緩存穿透和防用戶線程阻塞都是依靠返回舊值來完成的。所以如果沒有舊值中贝,同樣會全部阻塞囤捻,因此應(yīng)視情況盡量在系統(tǒng)啟動時將緩存內(nèi)容加載到內(nèi)存中。

在刷新緩存時邻寿,如果generateValueByKey方法出現(xiàn)異承粒或者返回了null,此時舊值不會更新绣否。

題外話:在使用內(nèi)存緩存時誊涯,切記拿到緩存值之后不要在業(yè)務(wù)代碼中對緩存直接做修改,因?yàn)榇藭r拿到的對象引用是指向緩存真正的內(nèi)容的蒜撮。如果需要直接在該對象上進(jìn)行修改暴构,則在獲取到緩存值后拷貝一份副本跪呈,然后傳遞該副本,進(jìn)行修改操作取逾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耗绿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砾隅,更是在濱河造成了極大的恐慌误阻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晴埂,死亡現(xiàn)場離奇詭異究反,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)儒洛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門精耐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琅锻,你說我怎么就攤上這事卦停。” “怎么了浅浮?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵沫浆,是天一觀的道長。 經(jīng)常有香客問我滚秩,道長专执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任郁油,我火速辦了婚禮本股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桐腌。我一直安慰自己拄显,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布案站。 她就那樣靜靜地躺著躬审,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蟆盐。 梳的紋絲不亂的頭發(fā)上承边,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音石挂,去河邊找鬼博助。 笑死,一個胖子當(dāng)著我的面吹牛痹愚,可吹牛的內(nèi)容都是我干的富岳。 我是一名探鬼主播蛔糯,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窖式!你這毒婦竟也來了蚁飒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脖镀,失蹤者是張志新(化名)和其女友劉穎飒箭,沒想到半個月后狼电,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜒灰,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年肩碟,在試婚紗的時候發(fā)現(xiàn)自己被綠了强窖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡削祈,死狀恐怖翅溺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情髓抑,我是刑警寧澤咙崎,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吨拍,受9級特大地震影響褪猛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羹饰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一伊滋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧队秩,春花似錦笑旺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸟蟹,卻和暖如春乌妙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戏锹。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工冠胯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锦针。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓荠察,卻偏偏與公主長得像置蜀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悉盆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容

  • com.google.common.cache 1盯荤、背景 緩存,在我們?nèi)粘i_發(fā)中是必不可少的一種解決性能問題的方法...
    拾壹北閱讀 22,310評論 0 25
  • 未經(jīng)歷牛熊十載余年来农,不足以言股海人生鞋真,經(jīng)得起浮層大浪,才能受的住頂禮榮耀沃于。十年磨一劍涩咖,一朝試鋒芒,股長如戰(zhàn)場繁莹,知己...
    老樊解股閱讀 255評論 0 1
  • 你還記得那個老鷹風(fēng)箏嗎咨演?風(fēng)箏不在了闸昨,但那件事我記得,終生不忘雪标,也忘不掉零院。 那次,由于我鬧脾氣不吃飯村刨,你一句...
    透過濾光片看藍(lán)天閱讀 248評論 0 1
  • 旗袍協(xié)會邀請函 協(xié)會簡介 寧德市蕉城區(qū)旗袍協(xié)會主管單位為蕉城區(qū)文體局告抄,是經(jīng)蕉城區(qū)民政局登記...
    學(xué)院詞人閱讀 1,046評論 1 2
  • 真正的高貴不是優(yōu)于別人,而是優(yōu)于過去的自己嵌牺。 還有接受現(xiàn)在的自己打洼。
    隨心所欲_e050閱讀 166評論 0 0