如何優(yōu)雅的設(shè)計(jì)和使用緩存?

1.確認(rèn)是否需要緩存

在使用緩存之前,需要確認(rèn)你的項(xiàng)目是否真的需要緩存。使用緩存會(huì)引入的一定的技術(shù)復(fù)雜度,后文也將會(huì)一一介紹這些復(fù)雜度。一般來說從兩個(gè)方面來個(gè)是否需要使用緩存:

CPU占用:如果你有某些應(yīng)用需要消耗大量的cpu去計(jì)算,比如正則表達(dá)式,如果你使用正則表達(dá)式比較頻繁,而其又占用了很多CPU的話,那你就應(yīng)該使用緩存將正則表達(dá)式的結(jié)果給緩存下來。

數(shù)據(jù)庫IO占用:如果你發(fā)現(xiàn)你的數(shù)據(jù)庫連接池比較空閑,那么不應(yīng)該用緩存。但是如果數(shù)據(jù)庫連接池比較繁忙,甚至經(jīng)常報(bào)出連接不夠的報(bào)警,那么是時(shí)候應(yīng)該考慮緩存了。筆者曾經(jīng)有個(gè)服務(wù),被很多其他服務(wù)調(diào)用,其他時(shí)間都還好,但是在每天早上10點(diǎn)的時(shí)候總是會(huì)報(bào)出數(shù)據(jù)庫連接池連接不夠的報(bào)警,經(jīng)過排查,發(fā)現(xiàn)有幾個(gè)服務(wù)選擇了在10點(diǎn)做定時(shí)任務(wù),大量的請(qǐng)求打過來,DB連接池不夠,從而報(bào)出連接池不夠的報(bào)警。這個(gè)時(shí)候有幾個(gè)選擇,我們可以通過擴(kuò)容機(jī)器來解決,也可以通過增加數(shù)據(jù)庫連接池來解決,但是沒有必要增加這些成本,因?yàn)橹挥性?0點(diǎn)的時(shí)候才會(huì)出現(xiàn)這個(gè)問題。后來引入了緩存,不僅解決了這個(gè)問題,而且還增加了讀的性能。

如果并沒有上述兩個(gè)問題,那么你不必為了增加緩存而緩存。

2.選擇合適的緩存

緩存又分進(jìn)程內(nèi)緩存和分布式緩存兩種。很多人包括筆者在開始選緩存框架的時(shí)候都感到了困惑:網(wǎng)上的緩存太多了,大家都吹噓自己很牛逼,我該怎么選擇呢?

2.1 選擇合適的進(jìn)程緩存

首先看看幾個(gè)比較常用的緩存的比較,具體原理可以參考你應(yīng)該知道的緩存進(jìn)化史:

比較項(xiàng) ConcurrentHashMap LRUMap Ehcache Guava Cache Caffeine 讀寫性能 很好,分段鎖 一般,全局加鎖 好 好,需要做淘汰操作 很好 淘汰算法 無 LRU,一般 支持多種淘汰算法,LRU,LFU,FIFO LRU,一般 W-TinyLFU, 很好 功能豐富程度 功能比較簡(jiǎn)單 功能比較單一 功能很豐富 功能很豐富,支持刷新和虛引用等 功能和Guava Cache類似 工具大小 jdk自帶類,很小 基于LinkedHashMap,較小 很大,最新版本1.4MB 是Guava工具類中的一個(gè)小部分,較小 一般,最新版本644KB 是否持久化 否 否 是 否 否 是否支持集群 否 否 是 否 否 對(duì)于ConcurrentHashMap來說,比較適合緩存比較固定不變的元素,且緩存的數(shù)量較小的。雖然從上面表格中比起來有點(diǎn)遜色,但是其由于是jdk自帶的類,在各種框架中依然有大量的使用,比如我們可以用來緩存我們反射的Method,Field等等;也可以緩存一些鏈接,防止其重復(fù)建立。在Caffeine中也是使用的ConcurrentHashMap來存儲(chǔ)元素。

對(duì)于LRUMap來說,如果不想引入第三方包,又想使用淘汰算法淘汰數(shù)據(jù),可以使用這個(gè)。

對(duì)于Ehcache來說,由于其jar包很大,較重量級(jí)。對(duì)于需要持久化和集群的一些功能的,可以選擇Ehcache。筆者沒怎么使用過這個(gè)緩存,如果要選擇的話,可以選擇分布式緩存來替代Ehcache。

對(duì)于Guava Cache來說,Guava這個(gè)jar包在很多Java應(yīng)用程序中都有大量的引入,所以很多時(shí)候其實(shí)是直接用就好了,并且其本身是輕量級(jí)的而且功能較為豐富,在不了解Caffeine的情況下可以選擇Guava Cache。

對(duì)于Caffeine來說,筆者是非常推薦的,其在命中率,讀寫性能上都比Guava Cache好很多,并且其API和Guava cache基本一致,甚至?xí)嘁稽c(diǎn)。在真實(shí)環(huán)境中使用Caffeine,取得過不錯(cuò)的效果。

總結(jié)一下:如果不需要淘汰算法則選擇ConcurrentHashMap,如果需要淘汰算法和一些豐富的API,這里推薦選擇Caffeine。

2.2 選擇合適的分布式緩存

這里選取三個(gè)比較出名的分布式緩存來作為比較,MemCache(沒有實(shí)戰(zhàn)使用過),Redis(在美團(tuán)又叫Squirrel),Tair(在美團(tuán)又叫Cellar)。不同的分布式緩存功能特性和實(shí)現(xiàn)原理方面有很大的差異,因此他們所適應(yīng)的場(chǎng)景也有所不同。

比較項(xiàng) MemCache Squirrel/Redis Cellar/Tair 數(shù)據(jù)結(jié)構(gòu) 只支持簡(jiǎn)單的Key-Value結(jié)構(gòu) String,Hash, List, Set, Sorted Set String,HashMap, List,Set 持久化 不支持 支持 支持 容量大小 數(shù)據(jù)純內(nèi)存,數(shù)據(jù)存儲(chǔ)不宜過多 數(shù)據(jù)全內(nèi)存,資源成本考量不宜超過100GB 可以配置全內(nèi)存或內(nèi)存+磁盤引擎,數(shù)據(jù)容量可無限擴(kuò)充 讀寫性能 很高 很高(RT0.5ms左右) String類型比較高(RT1ms左右),復(fù)雜類型比較慢(RT5ms左右) MemCache:這一塊接觸得比較少,不做過多的推薦。其吞吐量較大,但是支持的數(shù)據(jù)結(jié)構(gòu)較少,并且不支持持久化。

Redis:支持豐富的數(shù)據(jù)結(jié)構(gòu),讀寫性能很高,但是數(shù)據(jù)全內(nèi)存,必須要考慮資源成本,支持持久化。

Tair: 支持豐富的數(shù)據(jù)結(jié)構(gòu),讀寫性能較高,部分類型比較慢,理論上容量可以無限擴(kuò)充。

總結(jié):如果服務(wù)對(duì)延遲比較敏感,Map/Set數(shù)據(jù)也比較多的話,比較適合Redis。如果服務(wù)需要放入緩存量的數(shù)據(jù)很大,對(duì)延遲又不是特別敏感的話,那就可以選擇Tair。在美團(tuán)的很多應(yīng)用中對(duì)Tair都有應(yīng)用,在筆者的項(xiàng)目中使用其存放我們生成的支付token,支付碼,用來替代數(shù)據(jù)庫存儲(chǔ)。大部分的情況下兩者都可以選擇,互為替代。

3.多級(jí)緩存

很多人一想到緩存馬上腦子里面就會(huì)出現(xiàn)下面的圖:

Redis用來存儲(chǔ)熱點(diǎn)數(shù)據(jù),Redis中沒有的數(shù)據(jù)則直接去數(shù)據(jù)庫訪問。

在之前介紹本地緩存的時(shí)候,很多人都問我,我已經(jīng)有Redis了,我干嘛還需要了解Guava,Caffeine這些進(jìn)程緩存呢。我基本統(tǒng)一回復(fù)下面兩個(gè)答案:

Redis如果掛了或者使用老版本的Redis,其會(huì)進(jìn)行全量同步,此時(shí)Redis是不可用的,這個(gè)時(shí)候我們只能訪問數(shù)據(jù)庫,很容易造成雪崩。

訪問Redis會(huì)有一定的網(wǎng)絡(luò)I/O以及序列化反序列化,雖然性能很高但是其終究沒有本地方法快,可以將最熱的數(shù)據(jù)存放在本地,以便進(jìn)一步加快訪問速度。這個(gè)思路并不是我們做互聯(lián)網(wǎng)架構(gòu)獨(dú)有的,在計(jì)算機(jī)系統(tǒng)中使用L1,L2,L3多級(jí)緩存,用來減少對(duì)內(nèi)存的直接訪問,從而加快訪問速度。

所以如果僅僅是使用Redis,能滿足我們大部分需求,但是當(dāng)需要追求更高的性能以及更高的可用性的時(shí)候,那就不得不了解多級(jí)緩存。

3.1使用進(jìn)程緩存

對(duì)于進(jìn)程內(nèi)緩存,其本來受限于內(nèi)存的大小的限制,以及進(jìn)程緩存更新后其他緩存無法得知,所以一般來說進(jìn)程緩存適用于:

數(shù)據(jù)量不是很大,數(shù)據(jù)更新頻率較低,之前我們有個(gè)查詢商家名字的服務(wù),在發(fā)送短信的時(shí)候需要調(diào)用,由于商家名字變更頻率較低,并且就算是變更了沒有及時(shí)變更緩存,短信里面帶有老的商家名字客戶也能接受。利用Caffeine作為本地緩存,size設(shè)置為1萬,過期時(shí)間設(shè)置為1個(gè)小時(shí),基本能在高峰期解決問題。

如果數(shù)據(jù)量更新頻繁,也想使用進(jìn)程緩存的話,那么可以將其過期時(shí)間設(shè)置為較短,或者設(shè)置其較短的自動(dòng)刷新的時(shí)間。這些對(duì)于Caffeine或者Guava Cache來說都是現(xiàn)成的API。

3.2使用多級(jí)緩存

俗話說得好,世界上沒有什么是一個(gè)緩存解決不了的事,如果有,那就兩個(gè)。

一般來說我們選擇一個(gè)進(jìn)程緩存和一個(gè)分布式緩存來搭配做多級(jí)緩存,一般來說引入兩個(gè)也足夠了,如果使用三個(gè),四個(gè)的話,技術(shù)維護(hù)成本會(huì)很高,反而有可能會(huì)得不償失,如下圖所示:

利用Caffeine做一級(jí)緩存,Redis作為二級(jí)緩存。

首先去Caffeine中查詢數(shù)據(jù),如果有直接返回。如果沒有則進(jìn)行第2步。

再去Redis中查詢,如果查詢到了返回?cái)?shù)據(jù)并在Caffeine中填充此數(shù)據(jù)。如果沒有查到則進(jìn)行第3步。

最后去Mysql中查詢,如果查詢到了返回?cái)?shù)據(jù)并在Redis,Caffeine中依次填充此數(shù)據(jù)。

對(duì)于Caffeine的緩存,如果有數(shù)據(jù)更新,只能刪除更新數(shù)據(jù)的那臺(tái)機(jī)器上的緩存,其他機(jī)器只能通過超時(shí)來過期緩存,超時(shí)設(shè)定可以有兩種策略:

設(shè)置成寫入后多少時(shí)間后過期

設(shè)置成寫入后多少時(shí)間刷新

對(duì)于Redis的緩存更新,其他機(jī)器立馬可見,但是也必須要設(shè)置超時(shí)時(shí)間,其時(shí)間比Caffeine的過期長。

為了解決進(jìn)程內(nèi)緩存的問題,設(shè)計(jì)進(jìn)一步優(yōu)化:

通過Redis的pub/sub,可以通知其他進(jìn)程緩存對(duì)此緩存進(jìn)行刪除。如果Redis掛了或者訂閱機(jī)制不靠譜,依靠超時(shí)設(shè)定,依然可以做兜底處理。4.緩存更新

一般來說緩存的更新有兩種情況:

先刪除緩存,再更新數(shù)據(jù)庫。

先更新數(shù)據(jù)庫,再刪除緩存。 這兩種情況在業(yè)界,大家對(duì)其都有自己的看法。具體怎么使用還得看各自的取舍。當(dāng)然肯定會(huì)有人問為什么要?jiǎng)h除緩存呢?而不是更新緩存呢?你可以想想當(dāng)有多個(gè)并發(fā)的請(qǐng)求更新數(shù)據(jù),你并不能保證更新數(shù)據(jù)庫的順序和更新緩存的順序一致,那就會(huì)出現(xiàn)數(shù)據(jù)庫中和緩存中數(shù)據(jù)不一致的情況。所以一般來說考慮刪除緩存。

4.1先刪除緩存,再更新數(shù)據(jù)庫

對(duì)于一個(gè)更新操作簡(jiǎn)單來說,就是先去各級(jí)緩存進(jìn)行刪除,然后更新數(shù)據(jù)庫。這個(gè)操作有一個(gè)比較大的問題,在對(duì)緩存刪除完之后,有一個(gè)讀請(qǐng)求,這個(gè)時(shí)候由于緩存被刪除所以直接會(huì)讀庫,讀操作的數(shù)據(jù)是老的并且會(huì)被加載進(jìn)入緩存當(dāng)中,后續(xù)讀請(qǐng)求全部訪問的老數(shù)據(jù)。

對(duì)緩存的操作不論成功失敗都不能阻塞我們對(duì)數(shù)據(jù)庫的操作,那么很多時(shí)候刪除緩存可以用異步的操作,但是先刪除緩存不能很好的適用于這個(gè)場(chǎng)景。

先刪除緩存也有一個(gè)好處是,如果對(duì)數(shù)據(jù)庫操作失敗了,那么由于先刪除的緩存,最多只是造成Cache Miss。

4.2先更新數(shù)據(jù)庫,再刪除緩存(推薦)

如果我們使用更新數(shù)據(jù)庫,再刪除緩存就能避免上面的問題。但是同樣的引入了新的問題,試想一下有一個(gè)數(shù)據(jù)此時(shí)是沒有緩存的,所以查詢請(qǐng)求會(huì)直接落庫,更新操作在查詢請(qǐng)求之后,但是更新操作刪除數(shù)據(jù)庫操作在查詢完之后回填緩存之前,就會(huì)導(dǎo)致我們緩存中和數(shù)據(jù)庫出現(xiàn)緩存不一致。

為什么我們這種情況有問題,很多公司包括Facebook還會(huì)選擇呢?因?yàn)橐|發(fā)這個(gè)條件比較苛刻。

首先需要數(shù)據(jù)不在緩存中。

其次查詢操作需要在更新操作先到達(dá)數(shù)據(jù)庫。

最后查詢操作的回填比更新操作的刪除后觸發(fā),這個(gè)條件基本很難出現(xiàn),因?yàn)楦虏僮鞯谋緛碓诓樵儾僮髦螅话銇碚f更新操作比查詢操作稍慢。但是更新操作的刪除卻在查詢操作之后,所以這個(gè)情況比較少出現(xiàn)。

對(duì)比上面4.1的問題來說這種問題的概率很低,況且我們有超時(shí)機(jī)制保底所以基本能滿足我們的需求。如果真的需要追求完美,可以使用二階段提交,但是其成本和收益一般來說不成正比。

當(dāng)然還有個(gè)問題是如果我們刪除失敗了,緩存的數(shù)據(jù)就會(huì)和數(shù)據(jù)庫的數(shù)據(jù)不一致,那么我們就只能靠過期超時(shí)來進(jìn)行兜底。對(duì)此我們可以進(jìn)行優(yōu)化,如果刪除失敗的話 我們不能影響主流程那么我們可以將其放入隊(duì)列后續(xù)進(jìn)行異步刪除。

5.緩存挖坑三劍客

大家一聽到緩存有哪些注意事項(xiàng),肯定首先想到的是緩存穿透,緩存擊穿,緩存雪崩這三個(gè)挖坑的小能手,這里簡(jiǎn)單介紹一下他們具體是什么以及應(yīng)對(duì)的方法。

5.1緩存穿透

緩存穿透是指查詢的數(shù)據(jù)在數(shù)據(jù)庫是沒有的,那么在緩存中自然也沒有,所以,在緩存中查不到就會(huì)去數(shù)據(jù)庫取查詢,這樣的請(qǐng)求一多,那么我們的數(shù)據(jù)庫的壓力自然會(huì)增大。

為了避免這個(gè)問題,可以采取下面兩個(gè)手段:

約定:對(duì)于返回為NULL的依然緩存,對(duì)于拋出異常的返回不進(jìn)行緩存,注意不要把拋異常的也給緩存了。采用這種手段的會(huì)增加我們緩存的維護(hù)成本,需要在插入緩存的時(shí)候刪除這個(gè)空緩存,當(dāng)然我們可以通過設(shè)置較短的超時(shí)時(shí)間來解決這個(gè)問題。

2. 制定一些規(guī)則過濾一些不可能存在的數(shù)據(jù),小數(shù)據(jù)用BitMap,大數(shù)據(jù)可以用布隆過濾器,比如你的訂單ID 明顯是在一個(gè)范圍1-1000,如果不是1-1000之內(nèi)的數(shù)據(jù)那其實(shí)可以直接給過濾掉。

5.2緩存擊穿

對(duì)于某些key設(shè)置了過期時(shí)間,但是其是熱點(diǎn)數(shù)據(jù),如果某個(gè)key失效,可能大量的請(qǐng)求打過來,緩存未命中,然后去數(shù)據(jù)庫訪問,此時(shí)數(shù)據(jù)庫訪問量會(huì)急劇增加。

為了避免這個(gè)問題,我們可以采取下面的兩個(gè)手段:

加分布式鎖:加載數(shù)據(jù)的時(shí)候可以利用分布式鎖鎖住這個(gè)數(shù)據(jù)的Key,在Redis中直接使用setNX操作即可,對(duì)于獲取到這個(gè)鎖的線程,查詢數(shù)據(jù)庫更新緩存,其他線程采取重試策略,這樣數(shù)據(jù)庫不會(huì)同時(shí)受到很多線程訪問同一條數(shù)據(jù)。

異步加載:由于緩存擊穿是熱點(diǎn)數(shù)據(jù)才會(huì)出現(xiàn)的問題,可以對(duì)這部分熱點(diǎn)數(shù)據(jù)采取到期自動(dòng)刷新的策略,而不是到期自動(dòng)淘汰。淘汰其實(shí)也是為了數(shù)據(jù)的時(shí)效性,所以采用自動(dòng)刷新也可以。

5.3緩存雪崩

緩存雪崩是指緩存不可用或者大量緩存由于超時(shí)時(shí)間相同在同一時(shí)間段失效,大量請(qǐng)求直接訪問數(shù)據(jù)庫,數(shù)據(jù)庫壓力過大導(dǎo)致系統(tǒng)雪崩。

為了避免這個(gè)問題,我們采取下面的手段:

增加緩存系統(tǒng)可用性,通過監(jiān)控關(guān)注緩存的健康程度,根據(jù)業(yè)務(wù)量適當(dāng)?shù)臄U(kuò)容緩存。

采用多級(jí)緩存,不同級(jí)別緩存設(shè)置的超時(shí)時(shí)間不同,及時(shí)某個(gè)級(jí)別緩存都過期,也有其他級(jí)別緩存兜底。

緩存的過期時(shí)間可以取個(gè)隨機(jī)值,比如以前是設(shè)置10分鐘的超時(shí)時(shí)間,那每個(gè)Key都可以隨機(jī)8-13分鐘過期,盡量讓不同Key的過期時(shí)間不同。

6.緩存污染

緩存污染一般出現(xiàn)在我們使用本地緩存中,可以想象,在本地緩存中如果你獲得了緩存,但是你接下來修改了這個(gè)數(shù)據(jù),但是這個(gè)數(shù)據(jù)并沒有更新在數(shù)據(jù)庫,這樣就造成了緩存污染:

上面的代碼就造成了緩存污染,通過id獲取Customer,但是需求需要修改Customer的名字,所以開發(fā)人員直接在取出來的對(duì)象中直接修改,這個(gè)Customer對(duì)象就會(huì)被污染,其他線程取出這個(gè)數(shù)據(jù)就是錯(cuò)誤的數(shù)據(jù)。要想避免這個(gè)問題需要開發(fā)人員從編碼上注意,并且代碼必須經(jīng)過嚴(yán)格的review,以及全方位的回歸測(cè)試,才能從一定程度上解決這個(gè)問題。

7.序列化

序列化是很多人都不注意的一個(gè)問題,很多人忽略了序列化的問題,上線之后馬上報(bào)出一下奇怪的錯(cuò)誤異常,造成了不必要的損失,最后一排查都是序列化的問題。列舉幾個(gè)序列化常見的問題:

key-value對(duì)象過于復(fù)雜導(dǎo)致序列化不支持:筆者之前出過一個(gè)問題,在美團(tuán)的Tair內(nèi)部默認(rèn)是使用protostuff進(jìn)行序列化,而美團(tuán)使用的通訊框架是thfift,thrift的TO是自動(dòng)生成的,這個(gè)TO里面很多復(fù)雜的數(shù)據(jù)結(jié)構(gòu),但是將其存放到了Tair中。查詢的時(shí)候反序列化也沒有報(bào)錯(cuò),單測(cè)也通過,但是到qa測(cè)試的時(shí)候發(fā)現(xiàn)這一塊功能有問題,發(fā)現(xiàn)有個(gè)字段是boolean類型默認(rèn)是false,把它改成true之后,序列化到tair中再反序列化還是false。定位到是protostuff對(duì)于復(fù)雜結(jié)構(gòu)的對(duì)象(比如數(shù)組,List等等)支持不是很好,會(huì)造成一定的問題。后來對(duì)這個(gè)TO進(jìn)行了轉(zhuǎn)換,用普通的Java對(duì)象就能進(jìn)行正確的序列化反序列化。

添加了字段或者刪除了字段,導(dǎo)致上線之后老的緩存獲取的時(shí)候反序列化報(bào)錯(cuò),或者出現(xiàn)一些數(shù)據(jù)移位。

不同的JVM的序列化不同,如果你的緩存有不同的服務(wù)都在共同使用(不提倡),那么需要注意不同JVM可能會(huì)對(duì)Class內(nèi)部的Field排序不同,而影響序列化。比如下面的代碼,在Jdk7和Jdk8中對(duì)象A的排列順序不同,最終會(huì)導(dǎo)致反序列化結(jié)果出現(xiàn)問題:

//jdk 7

class A{

int a;

int b;

}

//jdk 8

class A{

int b;

int a;

}

序列化的問題必須得到重視,解決的辦法有如下幾點(diǎn):

測(cè)試:對(duì)于序列化需要進(jìn)行全面的測(cè)試,如果有不同的服務(wù)并且他們的JVM不同那么你也需要做這一塊的測(cè)試,在上面的問題中筆者的單測(cè)通過的原因是用的默認(rèn)數(shù)據(jù)false,所以根本沒有測(cè)試true的情況,還好QA給力,將其給測(cè)試出來了。

對(duì)于不同的序列化框架都有自己不同的原理,對(duì)于添加字段之后如果當(dāng)前序列化框架不能兼容老的,那么可以換個(gè)序列化框架。 對(duì)于protostuff來說他是按照Field的順序來進(jìn)行反序列化的,對(duì)于添加字段我們需要放到末尾,也就是不能插在中間,否則會(huì)出現(xiàn)錯(cuò)誤。對(duì)于刪除字段來說,用@Deprecated注解進(jìn)行標(biāo)注棄用,如果貿(mào)然刪除,除非是最后一個(gè)字段,否則肯定會(huì)出現(xiàn)序列化異常。

可以使用雙寫來避免,對(duì)于每個(gè)緩存的key值可以加上版本號(hào),每次上線版本號(hào)都加1,比如現(xiàn)在線上的緩存用的是Key_1,即將要上線的是Key_2,上線之后對(duì)緩存的添加是會(huì)寫新老兩個(gè)不同的版本(Key_1,Key_2)的Key-Value,讀取數(shù)據(jù)還是讀取老版本Key_1的數(shù)據(jù),假設(shè)之前的緩存的過期時(shí)間是半個(gè)小時(shí),那么上線半個(gè)小時(shí)之后,之前的老緩存存量的數(shù)據(jù)都會(huì)被淘汰,此時(shí)線上老緩存和新緩存他們的數(shù)據(jù)基本是一樣的,切換讀操作到新緩存,然后停止雙寫。采用這種方法基本能平滑過渡新老Model交替,但是不好的點(diǎn)就是需要短暫的維護(hù)兩套新老Model,下次上線的時(shí)候需要?jiǎng)h除掉老Model,增加了維護(hù)成本。

8. GC調(diào)優(yōu)

對(duì)于大量使用本地緩存的應(yīng)用,由于涉及到緩存淘汰,那么GC問題必定是常事。如果出現(xiàn)GC較多,STW時(shí)間較長,那么必定會(huì)影響服務(wù)可用性。這一塊給出下面幾點(diǎn)建議:

經(jīng)常查看GC監(jiān)控,如何發(fā)現(xiàn)不正常,需要想辦法對(duì)其進(jìn)行優(yōu)化。

對(duì)于CMS垃圾收集器,如果發(fā)現(xiàn)remark過長,如果是大量本地緩存應(yīng)用的話這個(gè)過長應(yīng)該很正常,因?yàn)樵诓l(fā)階段很容易有很多新對(duì)象進(jìn)入緩存,從而remark階段掃描很耗時(shí),remark又會(huì)暫停。可以開啟-XX:CMSScavengeBeforeRemark,在remark階段前進(jìn)行一次YGC,從而減少remark階段掃描gc root的開銷。

可以使用G1垃圾收集器,通過-XX:MaxGCPauseMillis設(shè)置最大停頓時(shí)間,提高服務(wù)可用性。

9. 緩存的監(jiān)控

很多人對(duì)于緩存的監(jiān)控也比較忽略,基本上線之后如果不報(bào)錯(cuò)然后就默認(rèn)他就生效了。但是存在這個(gè)問題,很多人由于經(jīng)驗(yàn)不足,有可能設(shè)置了不恰當(dāng)?shù)倪^期時(shí)間,或者不恰當(dāng)?shù)木彺娲笮?dǎo)致緩存命中率不高,讓緩存就成為了代碼中的一個(gè)裝飾品。所以對(duì)于緩存各種指標(biāo)的監(jiān)控,也比較重要,通過其不同的指標(biāo)數(shù)據(jù),我們可以對(duì)緩存的參數(shù)進(jìn)行優(yōu)化,從而讓緩存達(dá)到最優(yōu)化:

上面的代碼中用來記錄get操作的,通過Cat記錄了獲取緩存成功,緩存不存在,緩存過期,緩存失敗(獲取緩存時(shí)如果拋出異常,則叫失敗),通過這些指標(biāo),我們就能統(tǒng)計(jì)出命中率,我們調(diào)整過期時(shí)間和大小的時(shí)候就可以參考這些指標(biāo)進(jìn)行優(yōu)化。

10. 一款好的框架

一個(gè)好的劍客沒有一把好劍怎么行呢?如果要使用好緩存,一個(gè)好的框架也必不可少。在最開始使用的時(shí)候大家使用緩存都用一些util,把緩存的邏輯寫在業(yè)務(wù)邏輯中:

上面的代碼把緩存的邏輯耦合在業(yè)務(wù)邏輯當(dāng)中,如果我們要增加成多級(jí)緩存那就需要修改我們的業(yè)務(wù)邏輯,不符合開閉原則,所以引入一個(gè)好的框架是不錯(cuò)的選擇。

推薦大家使用JetCache這款開源框架,其實(shí)現(xiàn)了Java緩存規(guī)范JSR107并且支持自動(dòng)刷新等高級(jí)功能。筆者參考JetCache結(jié)合Spring Cache, 監(jiān)控框架Cat以及美團(tuán)的熔斷限流框架Rhino實(shí)現(xiàn)了一套自有的緩存框架,讓操作緩存,打點(diǎn)監(jiān)控,熔斷降級(jí),業(yè)務(wù)人員無需關(guān)心。上面的代碼可以優(yōu)化成:

對(duì)于一些監(jiān)控?cái)?shù)據(jù)也能輕松從大盤上看到:

最后

想要真正的使用好一個(gè)緩存,必須要掌握很多的知識(shí),并不是看幾個(gè)Redis原理分析,就能把Redis緩存用得爐火純青。對(duì)于不同場(chǎng)景,緩存有各自不同的用法,同樣的不同的緩存也有自己的調(diào)優(yōu)策略,進(jìn)程內(nèi)緩存你需要關(guān)注的是他的淘汰算法和GC調(diào)優(yōu),以及要避免緩存污染等。分布式緩存你需要關(guān)注的是他的高可用,如果其不可用了如何進(jìn)行降級(jí),以及一些序列化的問題。一個(gè)好的框架也是必不可少的,對(duì)其如果使用得當(dāng)再加上上面介紹的經(jīng)驗(yàn),相信能讓你很好的駕馭住這頭野馬——緩存。如何優(yōu)雅的設(shè)計(jì)和使用緩存?

如果你現(xiàn)在在JAVA這條路上,可以來領(lǐng)取Java工程化、高性能及分布式、高性能、高架構(gòu)、性能調(diào)優(yōu)、Spring,MyBatis,Netty源碼分析和大數(shù)據(jù)等資料。QQ群為:835638062

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

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