新聞中心
在之前的文章《你應(yīng)該知道的緩存進(jìn)化史》中介紹了愛奇藝的緩存架構(gòu)和緩存的進(jìn)化歷史。

【稿件】俗話說得好,工欲善其事,必先利其器,有了好的工具肯定得知道如何用好這些工具,本篇將分為如下幾個(gè)方面介紹如何利用好緩存:
- 你真的需要緩存嗎
- 如何選擇合適的緩存
- 多級(jí)緩存
- 緩存更新
- 緩存挖坑三劍客
- 緩存污染
- 序列化
- GC調(diào)優(yōu)
- 緩存的監(jiān)控
- 一款好的框架
- 總結(jié)
你真的需要緩存嗎
在使用緩存之前,需要確認(rèn)你的項(xiàng)目是否真的需要緩存。使用緩存會(huì)引入一定的技術(shù)復(fù)雜度,一般來說從兩個(gè)方面來判斷是否需要使用緩存:
CPU 占用
如果你有某些應(yīng)用需要消耗大量的 CPU 去計(jì)算,比如正則表達(dá)式;如果你使用正則表達(dá)式比較頻繁,而它又占用了很多 CPU 的話,那你就應(yīng)該使用緩存將正則表達(dá)式的結(jié)果給緩存下來。
數(shù)據(jù)庫(kù) IO 占用
如果你發(fā)現(xiàn)你的數(shù)據(jù)庫(kù)連接池比較空閑,可以不用緩存。但是如果數(shù)據(jù)庫(kù)連接池比較繁忙,甚至經(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ù)庫(kù)連接池連接不夠的報(bào)警。
經(jīng)過排查,我發(fā)現(xiàn)有幾個(gè)服務(wù)選擇了在 10 點(diǎn)做定時(shí)任務(wù),大量的請(qǐng)求打過來,DB 連接池不夠,從而產(chǎn)生連接池不夠的報(bào)警。
這個(gè)時(shí)候有幾個(gè)選擇,我們可以通過擴(kuò)容機(jī)器來解決,也可以通過增加數(shù)據(jù)庫(kù)連接池來解決。
但是沒有必要增加這些成本,因?yàn)橹挥性?10 點(diǎn)的時(shí)候才會(huì)出現(xiàn)這個(gè)問題。后來引入了緩存,不僅解決了這個(gè)問題,而且還增加了讀的性能。
如果并沒有上述兩個(gè)問題,那么你不必為了增加緩存而緩存。
如何選擇合適的緩存
緩存分為進(jìn)程內(nèi)緩存和分布式緩存。包括筆者在內(nèi)的很多人在開始選緩存框架的時(shí)候都會(huì)感到困惑:網(wǎng)上的緩存太多了,大家都吹噓自己很牛逼,我該怎么選擇呢?
選擇合適的進(jìn)程緩存
首先看幾個(gè)比較常用緩存的比較,具體原理可以參考《你應(yīng)該知道的緩存進(jìn)化史》:
對(duì)于 ConcurrentHashMap 來說,比較適合緩存比較固定不變的元素,且緩存的數(shù)量較小的。
雖然從上面表格中比起來有點(diǎn)遜色,但是由于它是 JDK 自帶的類,在各種框架中依然有大量的使用。
比如我們可以用來緩存反射的 Method,F(xiàn)ield 等等;也可以緩存一些鏈接,防止重復(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í)候直接用就好了,并且它本身是輕量級(jí)的而且功能較為豐富,在不了解 Caffeine 的情況下可以選擇 Guava Cache。
對(duì)于 Caffeine 來說,筆者是非常推薦的,它在***率,讀寫性能上都比 Guava Cache 好很多。
并且它的 API 和 Guava Cache 基本一致,甚至?xí)嘁稽c(diǎn)。在真實(shí)環(huán)境中使用 Caffeine,取得過不錯(cuò)的效果。
總結(jié)一下:如果不需要淘汰算法則選擇 ConcurrentHashMap;如果需要淘汰算法和一些豐富的 API,這里推薦選擇 Caffeine。
選擇合適的分布式緩存
這里我選取三個(gè)比較出名的分布式緩存來作為比較,MemCache(沒有實(shí)戰(zhàn)使用過),Redis(在美團(tuán)又叫 Squirrel),Tair(在美團(tuán)又叫 Cellar)。
不同的分布式緩存功能特性和實(shí)現(xiàn)原理方面有很大的差異,因此它們所適應(yīng)的場(chǎng)景也有所不同:
- 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ù)庫(kù)存儲(chǔ)。大部分的情況下兩者都可以選擇,互為替代。
多級(jí)緩存
一說到緩存,很多人腦子里面馬上就會(huì)出現(xiàn)下面的圖:
Redis 用來存儲(chǔ)熱點(diǎn)數(shù)據(jù),Redis 中沒有的數(shù)據(jù)則直接去數(shù)據(jù)庫(kù)訪問。
在之前介紹本地緩存的時(shí)候,很多人都問我,我已經(jīng)有 Redis 了,我為什么還需要了解 Guava,Caffeine 這些進(jìn)程緩存呢?
我統(tǒng)一回復(fù)下,有如下兩個(gè)原因:
- Redis 如果掛了或者使用老版本的 Redis,會(huì)進(jìn)行全量同步,此時(shí) Redis 是不可用的,這個(gè)時(shí)候我們只能訪問數(shù)據(jù)庫(kù),很容易造成雪崩。
- 訪問 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í)緩存。
使用進(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。
使用多級(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 的過期長(zhǎng)。
為了解決進(jìn)程內(nèi)緩存的問題,設(shè)計(jì)進(jìn)一步優(yōu)化:
通過 Redis 的 Pub/Sub,可以通知其他進(jìn)程緩存對(duì)此緩存進(jìn)行刪除。如果 Redis 掛了或者訂閱機(jī)制不靠譜,依靠超時(shí)設(shè)定,依然可以做兜底處理。
緩存更新
一般來說緩存的更新有兩種情況:
- 先刪除緩存,再更新數(shù)據(jù)庫(kù)。
- 先更新數(shù)據(jù)庫(kù),再刪除緩存。
這兩種情況在業(yè)界,大家都有自己的看法。具體怎么使用還得看各自的取舍。當(dāng)然肯定有人會(huì)問為什么要?jiǎng)h除緩存呢?而不是更新緩存呢?
當(dāng)有多個(gè)并發(fā)的請(qǐng)求更新數(shù)據(jù),你并不能保證更新數(shù)據(jù)庫(kù)的順序和更新緩存的順序一致,那么就會(huì)出現(xiàn)數(shù)據(jù)庫(kù)中和緩存中數(shù)據(jù)不一致的情況。所以一般來說考慮刪除緩存。
先刪除緩存,再更新數(shù)據(jù)庫(kù)
對(duì)于一個(gè)更新操作簡(jiǎn)單來說,就是先對(duì)各級(jí)緩存進(jìn)行刪除,然后更新數(shù)據(jù)庫(kù)。
這個(gè)操作有一個(gè)比較大的問題,在對(duì)緩存刪除完之后,有一個(gè)讀請(qǐng)求,這個(gè)時(shí)候由于緩存被刪除所以直接會(huì)讀庫(kù),讀操作的數(shù)據(jù)是老的并且會(huì)被加載進(jìn)入緩存當(dāng)中,后續(xù)讀請(qǐng)求全部訪問的老數(shù)據(jù)。
對(duì)緩存的操作不論成功失敗都不能阻塞我們對(duì)數(shù)據(jù)庫(kù)的操作,那么很多時(shí)候刪除緩存可以用異步的操作,但是先刪除緩存不能很好的適用于這個(gè)場(chǎng)景。
先刪除緩存也有一個(gè)好處是,如果對(duì)數(shù)據(jù)庫(kù)操作失敗了,那么由于先刪除的緩存,最多只是造成 Cache Miss。
先更新數(shù)據(jù)庫(kù),再刪除緩存(推薦)
如果我們使用更新數(shù)據(jù)庫(kù),再刪除緩存就能避免上面的問題。但是同樣引入了新的問題。
試想一下有一個(gè)數(shù)據(jù)此時(shí)是沒有緩存的,所以查詢請(qǐng)求會(huì)直接落庫(kù),更新操作在查詢請(qǐng)求之后,但是更新操作刪除數(shù)據(jù)庫(kù)操作在查詢完之后回填緩存之前,就會(huì)導(dǎo)致我們緩存中和數(shù)據(jù)庫(kù)出現(xiàn)緩存不一致。
為什么我們這種情況有問題,很多公司包括 Facebook 還會(huì)選擇呢?因?yàn)橐|發(fā)這個(gè)條件比較苛刻:
- 首先需要數(shù)據(jù)不在緩存中。
- 其次查詢操作需要在更新操作先到達(dá)數(shù)據(jù)庫(kù)。
- ***查詢操作的回填比更新操作的刪除后觸發(fā),這個(gè)條件基本很難出現(xiàn),因?yàn)楦虏僮鞯谋緛碓诓樵儾僮髦?,一般來說更新操作比查詢操作稍慢。但是更新操作的刪除卻在查詢操作之后,所以這個(gè)情況比較少出現(xiàn)。
對(duì)比上面先刪除緩存,再更新數(shù)據(jù)庫(kù)的問題來說這種問題出現(xiàn)的概率很低,況且我們有超時(shí)機(jī)制保底所以基本能滿足我們的需求。
如果真的需要追求***,可以使用二階段提交,但是成本和收益一般來說不成正比。
當(dāng)然還有個(gè)問題是如果我們刪除失敗了,緩存的數(shù)據(jù)就會(huì)和數(shù)據(jù)庫(kù)的數(shù)據(jù)不一致,那么我們就只能靠過期超時(shí)來進(jìn)行兜底。
對(duì)此我們可以進(jìn)行優(yōu)化,如果刪除失敗的話 我們不能影響主流程那么我們可以將其放入隊(duì)列后續(xù)進(jìn)行異步刪除。
緩存挖坑三劍客
大家一聽到緩存有哪些注意事項(xiàng),首先想到的肯定是緩存穿透,緩存擊穿,緩存雪崩這三個(gè)挖坑的小能手,這里簡(jiǎn)單介紹一下他們具體是什么以及應(yīng)對(duì)的方法。
緩存穿透
緩存穿透是指查詢的數(shù)據(jù)在數(shù)據(jù)庫(kù)是沒有的,那么在緩存中自然也沒有,所以在緩存中查不到就會(huì)去數(shù)據(jù)庫(kù)查詢,這樣的請(qǐng)求一多,我們數(shù)據(jù)庫(kù)的壓力自然會(huì)增大。
為了避免這個(gè)問題,可以采取下面兩個(gè)手段:
約定:對(duì)于返回為 NULL 的依然緩存,對(duì)于拋出異常的返回不進(jìn)行緩存,注意不要把拋異常的也給緩存了。
采用這種手段會(huì)增加我們緩存的維護(hù)成本,需要在插入緩存的時(shí)候刪除這個(gè)空緩存,當(dāng)然我們可以通過設(shè)置較短的超時(shí)時(shí)間來解決這個(gè)問題。
制定一些規(guī)則過濾一些不可能存在的數(shù)據(jù),小數(shù)據(jù)用 BitMap,大數(shù)據(jù)可以用布隆過濾器。
比如你的訂單 ID 明顯是在一個(gè)范圍 1-1000,如果不是 1-1000 之內(nèi)的數(shù)據(jù)那其實(shí)可以直接給過濾掉。
緩存擊穿
對(duì)于某些 Key 設(shè)置了過期時(shí)間,但是它是熱點(diǎn)數(shù)據(jù),如果某個(gè) Key 失效,可能大量的請(qǐng)求打過來,緩存未***,然后去數(shù)據(jù)庫(kù)訪問,此時(shí)數(shù)據(jù)庫(kù)訪問量會(huì)急劇增加。
為了避免這個(gè)問題,我們可以采取下面的兩個(gè)手段:
- 加分布式鎖:加載數(shù)據(jù)的時(shí)候可以利用分布式鎖鎖住這個(gè)數(shù)據(jù)的 Key,在 Redis 中直接使用 SetNX 操作即可。
對(duì)于獲取到這個(gè)鎖的線程,查詢數(shù)據(jù)庫(kù)更新緩存,其他線程采取重試策略,這樣數(shù)據(jù)庫(kù)不會(huì)同時(shí)受到很多線程訪問同一條數(shù)據(jù)。
- 異步加載:由于緩存擊穿是熱點(diǎn)數(shù)據(jù)才會(huì)出現(xiàn)的問題,可以對(duì)這部分熱點(diǎn)數(shù)據(jù)采取到期自動(dòng)刷新的策略,而不是到期自動(dòng)淘汰。淘汰也是為了數(shù)據(jù)的時(shí)效性,所以采用自動(dòng)刷新也可以。
緩存雪崩
緩存雪崩是指緩存不可用或者大量緩存由于超時(shí)時(shí)間相同在同一時(shí)間段失效,大量請(qǐng)求直接訪問數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)壓力過大導(dǎo)致系統(tǒng)雪崩。
為了避免這個(gè)問題,我們采取下面的手段:
- 增加緩存系統(tǒng)可用性,通過監(jiān)控關(guān)注緩存的健康程度,根據(jù)業(yè)務(wù)量適當(dāng)?shù)臄U(kuò)容緩存。
- 采用多級(jí)緩存,不同級(jí)別緩存設(shè)置的超時(shí)時(shí)間不同,即使某個(gè)級(jí)別緩存都過期,也有其他級(jí)別緩存兜底。
- 緩存的 Key 值可以取個(gè)隨機(jī)值,比如以前是設(shè)置 10 分鐘的超時(shí)時(shí)間,那每個(gè) Key 都可以隨機(jī) 8-13 分鐘過期,盡量讓不同 Key 的過期時(shí)間不同。
緩存污染
緩存污染一般出現(xiàn)在我們使用本地緩存中??梢韵胂?,在本地緩存中如果你獲得了緩存,但是你接下來修改了這個(gè)數(shù)據(jù),這個(gè)數(shù)據(jù)卻并沒有更新在數(shù)據(jù)庫(kù),這樣就造成了緩存污染:
上面的代碼就造成了緩存污染,通過 ID 獲取 Customer,但是需求需要修改 Customer 的名字。
所以開發(fā)人員直接在取出來的對(duì)象中直接修改,這個(gè) Customer 對(duì)象就會(huì)被污染,其他線程取出這個(gè)數(shù)據(jù)就是錯(cuò)誤的數(shù)據(jù)。
要想避免這個(gè)問題需要開發(fā)人員從編碼上注意,并且代碼必須經(jīng)過嚴(yán)格的 Review,以及全方位的回歸測(cè)試,才能從一定程度上解決這個(gè)問題。
序列化
序列化是很多人都不注意的一個(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)這一塊功能有問題,有個(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 排序不同,而影響序列化。
比如(舉例,實(shí)際情況不一定如此)下面的代碼,在 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 交替,但是不好的就是需要短暫的維護(hù)兩套新老 Model,下次上線的時(shí)候需要?jiǎng)h除掉老 Model,這樣增加了維護(hù)成本。
GC 調(diào)優(yōu)
對(duì)于大量使用本地緩存的應(yīng)用,由于涉及到緩存淘汰,那么 GC 問題必定是常事。如果出現(xiàn) GC 較多,STW 時(shí)間較長(zhǎng),那么必定會(huì)影響服務(wù)可用性。
這一塊給出下面幾點(diǎn)建議:
- 經(jīng)常查看 GC 監(jiān)控,如何發(fā)現(xiàn)不正常,需要想辦法對(duì)其進(jìn)行優(yōu)化。
- 對(duì)于 CMS 垃圾收集算法,如果發(fā)現(xiàn) Remark 過長(zhǎng),如果是大量本地緩存應(yīng)用的話這個(gè)過長(zhǎng)應(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ù)可用性。
緩存的監(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á)到***化:
上面的代碼中用來記錄 Get 操作的,通過 Cat 記錄了獲取緩存成功,緩存不存在,緩存過期,緩存失敗(獲取緩存時(shí)如果拋出異常,則叫失敗)。
通過這些指標(biāo),我們就能統(tǒng)計(jì)出***率,我們調(diào)整過期時(shí)間和大小的時(shí)候就可以參考這些指標(biāo)進(jìn)行優(yōu)化。
一款好的框架
一個(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ù)也能輕松從大盤上看到:
總結(jié)
想要真正的使用好一個(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),相信能讓你很好的駕馭住這頭野馬——緩存。
【原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為.com】
新聞名稱:緩存這匹“野馬”,你駕馭得了嗎?
本文路徑:http://m.5511xx.com/article/cdippde.html


咨詢
建站咨詢
