1.確認是否需要緩存
在使用緩存之前确虱,需要確認你的項目是否真的需要緩存。使用緩存會引入的一定的技術(shù)復(fù)雜度替裆,后文也將會一一介紹這些復(fù)雜度校辩。一般來說從兩個方面來個是否需要使用緩存:
CPU占用:如果你有某些應(yīng)用需要消耗大量的cpu去計算,比如正則表達式辆童,如果你使用正則表達式比較頻繁宜咒,而其又占用了很多CPU的話,那你就應(yīng)該使用緩存將正則表達式的結(jié)果給緩存下來把鉴。
數(shù)據(jù)庫IO占用:如果你發(fā)現(xiàn)你的數(shù)據(jù)庫連接池比較空閑故黑,那么不應(yīng)該用緩存。但是如果數(shù)據(jù)庫連接池比較繁忙纸镊,甚至經(jīng)常報出連接不夠的報警倍阐,那么是時候應(yīng)該考慮緩存了。筆者曾經(jīng)有個服務(wù)逗威,被很多其他服務(wù)調(diào)用峰搪,其他時間都還好,但是在每天早上10點的時候總是會報出數(shù)據(jù)庫連接池連接不夠的報警凯旭,經(jīng)過排查概耻,發(fā)現(xiàn)有幾個服務(wù)選擇了在10點做定時任務(wù)使套,大量的請求打過來,DB連接池不夠鞠柄,從而報出連接池不夠的報警侦高。這個時候有幾個選擇,我們可以通過擴容機器來解決厌杜,也可以通過增加數(shù)據(jù)庫連接池來解決奉呛,但是沒有必要增加這些成本,因為只有在10點的時候才會出現(xiàn)這個問題夯尽。后來引入了緩存瞧壮,不僅解決了這個問題,而且還增加了讀的性能匙握。
如果并沒有上述兩個問題咆槽,那么你不必為了增加緩存而緩存。
2.選擇合適的緩存
緩存又分進程內(nèi)緩存和分布式緩存兩種圈纺。很多人包括筆者在開始選緩存框架的時候都感到了困惑:網(wǎng)上的緩存太多了秦忿,大家都吹噓自己很牛逼,我該怎么選擇呢蛾娶?
2.1 選擇合適的進程緩存
首先看看幾個比較常用的緩存的比較灯谣,具體原理可以參考你應(yīng)該知道的緩存進化史:
比較項 ConcurrentHashMap LRUMap Ehcache Guava Cache Caffeine 讀寫性能 很好,分段鎖 一般茫叭,全局加鎖 好 好酬屉,需要做淘汰操作 很好 淘汰算法 無 LRU,一般 支持多種淘汰算法,LRU,LFU,FIFO LRU揍愁,一般 W-TinyLFU, 很好 功能豐富程度 功能比較簡單 功能比較單一 功能很豐富 功能很豐富,支持刷新和虛引用等 功能和Guava Cache類似 工具大小 jdk自帶類杀饵,很小 基于LinkedHashMap莽囤,較小 很大,最新版本1.4MB 是Guava工具類中的一個小部分切距,較小 一般朽缎,最新版本644KB 是否持久化 否 否 是 否 否 是否支持集群 否 否 是 否 否 對于ConcurrentHashMap來說,比較適合緩存比較固定不變的元素谜悟,且緩存的數(shù)量較小的话肖。雖然從上面表格中比起來有點遜色,但是其由于是jdk自帶的類葡幸,在各種框架中依然有大量的使用,比如我們可以用來緩存我們反射的Method,Field等等;也可以緩存一些鏈接最筒,防止其重復(fù)建立。在Caffeine中也是使用的ConcurrentHashMap來存儲元素蔚叨。
對于LRUMap來說床蜘,如果不想引入第三方包辙培,又想使用淘汰算法淘汰數(shù)據(jù),可以使用這個邢锯。
對于Ehcache來說扬蕊,由于其jar包很大,較重量級丹擎。對于需要持久化和集群的一些功能的尾抑,可以選擇Ehcache。筆者沒怎么使用過這個緩存蒂培,如果要選擇的話蛮穿,可以選擇分布式緩存來替代Ehcache。
對于Guava Cache來說毁渗,Guava這個jar包在很多Java應(yīng)用程序中都有大量的引入践磅,所以很多時候其實是直接用就好了,并且其本身是輕量級的而且功能較為豐富灸异,在不了解Caffeine的情況下可以選擇Guava Cache府适。
對于Caffeine來說,筆者是非常推薦的肺樟,其在命中率檐春,讀寫性能上都比Guava Cache好很多,并且其API和Guava cache基本一致么伯,甚至?xí)嘁稽c疟暖。在真實環(huán)境中使用Caffeine,取得過不錯的效果田柔。
總結(jié)一下:如果不需要淘汰算法則選擇ConcurrentHashMap俐巴,如果需要淘汰算法和一些豐富的API,這里推薦選擇Caffeine硬爆。
2.2 選擇合適的分布式緩存
這里選取三個比較出名的分布式緩存來作為比較欣舵,MemCache(沒有實戰(zhàn)使用過),Redis(在美團又叫Squirrel)缀磕,Tair(在美團又叫Cellar)缘圈。不同的分布式緩存功能特性和實現(xiàn)原理方面有很大的差異,因此他們所適應(yīng)的場景也有所不同袜蚕。
比較項 MemCache Squirrel/Redis Cellar/Tair 數(shù)據(jù)結(jié)構(gòu) 只支持簡單的Key-Value結(jié)構(gòu) String,Hash, List, Set, Sorted Set String,HashMap, List糟把,Set 持久化 不支持 支持 支持 容量大小 數(shù)據(jù)純內(nèi)存,數(shù)據(jù)存儲不宜過多 數(shù)據(jù)全內(nèi)存牲剃,資源成本考量不宜超過100GB 可以配置全內(nèi)存或內(nèi)存+磁盤引擎遣疯,數(shù)據(jù)容量可無限擴充 讀寫性能 很高 很高(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)亡鼠,讀寫性能較高赏殃,部分類型比較慢,理論上容量可以無限擴充间涵。
總結(jié):如果服務(wù)對延遲比較敏感仁热,Map/Set數(shù)據(jù)也比較多的話,比較適合Redis勾哩。如果服務(wù)需要放入緩存量的數(shù)據(jù)很大抗蠢,對延遲又不是特別敏感的話,那就可以選擇Tair思劳。在美團的很多應(yīng)用中對Tair都有應(yīng)用迅矛,在筆者的項目中使用其存放我們生成的支付token,支付碼,用來替代數(shù)據(jù)庫存儲潜叛。大部分的情況下兩者都可以選擇秽褒,互為替代。
3.多級緩存
很多人一想到緩存馬上腦子里面就會出現(xiàn)下面的圖:
Redis用來存儲熱點數(shù)據(jù)威兜,Redis中沒有的數(shù)據(jù)則直接去數(shù)據(jù)庫訪問销斟。
在之前介紹本地緩存的時候,很多人都問我牡属,我已經(jīng)有Redis了票堵,我干嘛還需要了解Guava,Caffeine這些進程緩存呢逮栅。我基本統(tǒng)一回復(fù)下面兩個答案:
Redis如果掛了或者使用老版本的Redis,其會進行全量同步,此時Redis是不可用的窗宇,這個時候我們只能訪問數(shù)據(jù)庫措伐,很容易造成雪崩。
訪問Redis會有一定的網(wǎng)絡(luò)I/O以及序列化反序列化军俊,雖然性能很高但是其終究沒有本地方法快侥加,可以將最熱的數(shù)據(jù)存放在本地,以便進一步加快訪問速度粪躬。這個思路并不是我們做互聯(lián)網(wǎng)架構(gòu)獨有的担败,在計算機系統(tǒng)中使用L1,L2,L3多級緩存昔穴,用來減少對內(nèi)存的直接訪問,從而加快訪問速度提前。
所以如果僅僅是使用Redis吗货,能滿足我們大部分需求,但是當需要追求更高的性能以及更高的可用性的時候狈网,那就不得不了解多級緩存宙搬。
3.1使用進程緩存
對于進程內(nèi)緩存,其本來受限于內(nèi)存的大小的限制拓哺,以及進程緩存更新后其他緩存無法得知勇垛,所以一般來說進程緩存適用于:
數(shù)據(jù)量不是很大,數(shù)據(jù)更新頻率較低士鸥,之前我們有個查詢商家名字的服務(wù)闲孤,在發(fā)送短信的時候需要調(diào)用,由于商家名字變更頻率較低烤礁,并且就算是變更了沒有及時變更緩存讼积,短信里面帶有老的商家名字客戶也能接受。利用Caffeine作為本地緩存,size設(shè)置為1萬鸽凶,過期時間設(shè)置為1個小時币砂,基本能在高峰期解決問題。
如果數(shù)據(jù)量更新頻繁玻侥,也想使用進程緩存的話决摧,那么可以將其過期時間設(shè)置為較短,或者設(shè)置其較短的自動刷新的時間凑兰。這些對于Caffeine或者Guava Cache來說都是現(xiàn)成的API掌桩。
3.2使用多級緩存
俗話說得好,世界上沒有什么是一個緩存解決不了的事姑食,如果有波岛,那就兩個。
一般來說我們選擇一個進程緩存和一個分布式緩存來搭配做多級緩存音半,一般來說引入兩個也足夠了则拷,如果使用三個,四個的話曹鸠,技術(shù)維護成本會很高煌茬,反而有可能會得不償失,如下圖所示:
利用Caffeine做一級緩存彻桃,Redis作為二級緩存坛善。
首先去Caffeine中查詢數(shù)據(jù),如果有直接返回。如果沒有則進行第2步眠屎。
再去Redis中查詢剔交,如果查詢到了返回數(shù)據(jù)并在Caffeine中填充此數(shù)據(jù)。如果沒有查到則進行第3步改衩。
最后去Mysql中查詢岖常,如果查詢到了返回數(shù)據(jù)并在Redis,Caffeine中依次填充此數(shù)據(jù)燎字。
對于Caffeine的緩存腥椒,如果有數(shù)據(jù)更新,只能刪除更新數(shù)據(jù)的那臺機器上的緩存候衍,其他機器只能通過超時來過期緩存笼蛛,超時設(shè)定可以有兩種策略:
設(shè)置成寫入后多少時間后過期
設(shè)置成寫入后多少時間刷新
對于Redis的緩存更新,其他機器立馬可見蛉鹿,但是也必須要設(shè)置超時時間滨砍,其時間比Caffeine的過期長。
為了解決進程內(nèi)緩存的問題妖异,設(shè)計進一步優(yōu)化:
通過Redis的pub/sub惋戏,可以通知其他進程緩存對此緩存進行刪除。如果Redis掛了或者訂閱機制不靠譜他膳,依靠超時設(shè)定响逢,依然可以做兜底處理。4.緩存更新
一般來說緩存的更新有兩種情況:
先刪除緩存棕孙,再更新數(shù)據(jù)庫舔亭。
先更新數(shù)據(jù)庫,再刪除緩存蟀俊。 這兩種情況在業(yè)界钦铺,大家對其都有自己的看法。具體怎么使用還得看各自的取舍肢预。當然肯定會有人問為什么要刪除緩存呢矛洞?而不是更新緩存呢?你可以想想當有多個并發(fā)的請求更新數(shù)據(jù)烫映,你并不能保證更新數(shù)據(jù)庫的順序和更新緩存的順序一致沼本,那就會出現(xiàn)數(shù)據(jù)庫中和緩存中數(shù)據(jù)不一致的情況。所以一般來說考慮刪除緩存锭沟。
4.1先刪除緩存擅威,再更新數(shù)據(jù)庫
對于一個更新操作簡單來說,就是先去各級緩存進行刪除冈钦,然后更新數(shù)據(jù)庫。這個操作有一個比較大的問題李请,在對緩存刪除完之后瞧筛,有一個讀請求厉熟,這個時候由于緩存被刪除所以直接會讀庫,讀操作的數(shù)據(jù)是老的并且會被加載進入緩存當中较幌,后續(xù)讀請求全部訪問的老數(shù)據(jù)揍瑟。
對緩存的操作不論成功失敗都不能阻塞我們對數(shù)據(jù)庫的操作,那么很多時候刪除緩存可以用異步的操作乍炉,但是先刪除緩存不能很好的適用于這個場景绢片。
先刪除緩存也有一個好處是,如果對數(shù)據(jù)庫操作失敗了岛琼,那么由于先刪除的緩存底循,最多只是造成Cache Miss。
4.2先更新數(shù)據(jù)庫槐瑞,再刪除緩存(推薦)
如果我們使用更新數(shù)據(jù)庫熙涤,再刪除緩存就能避免上面的問題。但是同樣的引入了新的問題,試想一下有一個數(shù)據(jù)此時是沒有緩存的困檩,所以查詢請求會直接落庫祠挫,更新操作在查詢請求之后,但是更新操作刪除數(shù)據(jù)庫操作在查詢完之后回填緩存之前悼沿,就會導(dǎo)致我們緩存中和數(shù)據(jù)庫出現(xiàn)緩存不一致等舔。
為什么我們這種情況有問題,很多公司包括Facebook還會選擇呢糟趾?因為要觸發(fā)這個條件比較苛刻慌植。
首先需要數(shù)據(jù)不在緩存中。
其次查詢操作需要在更新操作先到達數(shù)據(jù)庫拉讯。
最后查詢操作的回填比更新操作的刪除后觸發(fā)涤浇,這個條件基本很難出現(xiàn),因為更新操作的本來在查詢操作之后魔慷,一般來說更新操作比查詢操作稍慢只锭。但是更新操作的刪除卻在查詢操作之后,所以這個情況比較少出現(xiàn)院尔。
對比上面4.1的問題來說這種問題的概率很低蜻展,況且我們有超時機制保底所以基本能滿足我們的需求。如果真的需要追求完美邀摆,可以使用二階段提交纵顾,但是其成本和收益一般來說不成正比。
當然還有個問題是如果我們刪除失敗了栋盹,緩存的數(shù)據(jù)就會和數(shù)據(jù)庫的數(shù)據(jù)不一致施逾,那么我們就只能靠過期超時來進行兜底。對此我們可以進行優(yōu)化,如果刪除失敗的話 我們不能影響主流程那么我們可以將其放入隊列后續(xù)進行異步刪除汉额。
5.緩存挖坑三劍客
大家一聽到緩存有哪些注意事項曹仗,肯定首先想到的是緩存穿透,緩存擊穿蠕搜,緩存雪崩這三個挖坑的小能手怎茫,這里簡單介紹一下他們具體是什么以及應(yīng)對的方法。
5.1緩存穿透
緩存穿透是指查詢的數(shù)據(jù)在數(shù)據(jù)庫是沒有的妓灌,那么在緩存中自然也沒有轨蛤,所以,在緩存中查不到就會去數(shù)據(jù)庫取查詢虫埂,這樣的請求一多祥山,那么我們的數(shù)據(jù)庫的壓力自然會增大。
為了避免這個問題告丢,可以采取下面兩個手段:
約定:對于返回為NULL的依然緩存枪蘑,對于拋出異常的返回不進行緩存,注意不要把拋異常的也給緩存了。采用這種手段的會增加我們緩存的維護成本岖免,需要在插入緩存的時候刪除這個空緩存岳颇,當然我們可以通過設(shè)置較短的超時時間來解決這個問題喊崖。
2. 制定一些規(guī)則過濾一些不可能存在的數(shù)據(jù)绒北,小數(shù)據(jù)用BitMap,大數(shù)據(jù)可以用布隆過濾器益老,比如你的訂單ID 明顯是在一個范圍1-1000闯参,如果不是1-1000之內(nèi)的數(shù)據(jù)那其實可以直接給過濾掉瞻鹏。
5.2緩存擊穿
對于某些key設(shè)置了過期時間,但是其是熱點數(shù)據(jù)鹿寨,如果某個key失效新博,可能大量的請求打過來,緩存未命中脚草,然后去數(shù)據(jù)庫訪問赫悄,此時數(shù)據(jù)庫訪問量會急劇增加。
為了避免這個問題馏慨,我們可以采取下面的兩個手段:
加分布式鎖:加載數(shù)據(jù)的時候可以利用分布式鎖鎖住這個數(shù)據(jù)的Key,在Redis中直接使用setNX操作即可埂淮,對于獲取到這個鎖的線程,查詢數(shù)據(jù)庫更新緩存写隶,其他線程采取重試策略倔撞,這樣數(shù)據(jù)庫不會同時受到很多線程訪問同一條數(shù)據(jù)。
異步加載:由于緩存擊穿是熱點數(shù)據(jù)才會出現(xiàn)的問題慕趴,可以對這部分熱點數(shù)據(jù)采取到期自動刷新的策略痪蝇,而不是到期自動淘汰鄙陡。淘汰其實也是為了數(shù)據(jù)的時效性,所以采用自動刷新也可以霹俺。
5.3緩存雪崩
緩存雪崩是指緩存不可用或者大量緩存由于超時時間相同在同一時間段失效柔吼,大量請求直接訪問數(shù)據(jù)庫,數(shù)據(jù)庫壓力過大導(dǎo)致系統(tǒng)雪崩丙唧。
為了避免這個問題,我們采取下面的手段:
增加緩存系統(tǒng)可用性,通過監(jiān)控關(guān)注緩存的健康程度觅玻,根據(jù)業(yè)務(wù)量適當?shù)臄U容緩存想际。
采用多級緩存,不同級別緩存設(shè)置的超時時間不同溪厘,及時某個級別緩存都過期胡本,也有其他級別緩存兜底。
緩存的過期時間可以取個隨機值畸悬,比如以前是設(shè)置10分鐘的超時時間侧甫,那每個Key都可以隨機8-13分鐘過期,盡量讓不同Key的過期時間不同蹋宦。
6.緩存污染
緩存污染一般出現(xiàn)在我們使用本地緩存中披粟,可以想象,在本地緩存中如果你獲得了緩存冷冗,但是你接下來修改了這個數(shù)據(jù)守屉,但是這個數(shù)據(jù)并沒有更新在數(shù)據(jù)庫,這樣就造成了緩存污染:
上面的代碼就造成了緩存污染蒿辙,通過id獲取Customer拇泛,但是需求需要修改Customer的名字,所以開發(fā)人員直接在取出來的對象中直接修改思灌,這個Customer對象就會被污染俺叭,其他線程取出這個數(shù)據(jù)就是錯誤的數(shù)據(jù)。要想避免這個問題需要開發(fā)人員從編碼上注意泰偿,并且代碼必須經(jīng)過嚴格的review熄守,以及全方位的回歸測試,才能從一定程度上解決這個問題甜奄。
7.序列化
序列化是很多人都不注意的一個問題柠横,很多人忽略了序列化的問題,上線之后馬上報出一下奇怪的錯誤異常课兄,造成了不必要的損失牍氛,最后一排查都是序列化的問題。列舉幾個序列化常見的問題:
key-value對象過于復(fù)雜導(dǎo)致序列化不支持:筆者之前出過一個問題烟阐,在美團的Tair內(nèi)部默認是使用protostuff進行序列化搬俊,而美團使用的通訊框架是thfift紊扬,thrift的TO是自動生成的,這個TO里面很多復(fù)雜的數(shù)據(jù)結(jié)構(gòu)唉擂,但是將其存放到了Tair中餐屎。查詢的時候反序列化也沒有報錯,單測也通過玩祟,但是到qa測試的時候發(fā)現(xiàn)這一塊功能有問題腹缩,發(fā)現(xiàn)有個字段是boolean類型默認是false,把它改成true之后空扎,序列化到tair中再反序列化還是false藏鹊。定位到是protostuff對于復(fù)雜結(jié)構(gòu)的對象(比如數(shù)組,List等等)支持不是很好转锈,會造成一定的問題盘寡。后來對這個TO進行了轉(zhuǎn)換,用普通的Java對象就能進行正確的序列化反序列化撮慨。
添加了字段或者刪除了字段竿痰,導(dǎo)致上線之后老的緩存獲取的時候反序列化報錯,或者出現(xiàn)一些數(shù)據(jù)移位砌溺。
不同的JVM的序列化不同影涉,如果你的緩存有不同的服務(wù)都在共同使用(不提倡),那么需要注意不同JVM可能會對Class內(nèi)部的Field排序不同抚吠,而影響序列化常潮。比如下面的代碼,在Jdk7和Jdk8中對象A的排列順序不同楷力,最終會導(dǎo)致反序列化結(jié)果出現(xiàn)問題:
//jdk 7
class A{
int a;
int b;
}
//jdk 8
class A{
int b;
int a;
}
序列化的問題必須得到重視喊式,解決的辦法有如下幾點:
測試:對于序列化需要進行全面的測試,如果有不同的服務(wù)并且他們的JVM不同那么你也需要做這一塊的測試萧朝,在上面的問題中筆者的單測通過的原因是用的默認數(shù)據(jù)false岔留,所以根本沒有測試true的情況,還好QA給力检柬,將其給測試出來了献联。
對于不同的序列化框架都有自己不同的原理,對于添加字段之后如果當前序列化框架不能兼容老的何址,那么可以換個序列化框架里逆。 對于protostuff來說他是按照Field的順序來進行反序列化的,對于添加字段我們需要放到末尾用爪,也就是不能插在中間原押,否則會出現(xiàn)錯誤。對于刪除字段來說偎血,用@Deprecated注解進行標注棄用诸衔,如果貿(mào)然刪除盯漂,除非是最后一個字段,否則肯定會出現(xiàn)序列化異常笨农。
可以使用雙寫來避免就缆,對于每個緩存的key值可以加上版本號,每次上線版本號都加1谒亦,比如現(xiàn)在線上的緩存用的是Key_1竭宰,即將要上線的是Key_2,上線之后對緩存的添加是會寫新老兩個不同的版本(Key_1,Key_2)的Key-Value,讀取數(shù)據(jù)還是讀取老版本Key_1的數(shù)據(jù),假設(shè)之前的緩存的過期時間是半個小時诊霹,那么上線半個小時之后羞延,之前的老緩存存量的數(shù)據(jù)都會被淘汰,此時線上老緩存和新緩存他們的數(shù)據(jù)基本是一樣的,切換讀操作到新緩存脾还,然后停止雙寫。采用這種方法基本能平滑過渡新老Model交替入愧,但是不好的點就是需要短暫的維護兩套新老Model鄙漏,下次上線的時候需要刪除掉老Model,增加了維護成本棺蛛。
8. GC調(diào)優(yōu)
對于大量使用本地緩存的應(yīng)用怔蚌,由于涉及到緩存淘汰,那么GC問題必定是常事旁赊。如果出現(xiàn)GC較多桦踊,STW時間較長,那么必定會影響服務(wù)可用性终畅。這一塊給出下面幾點建議:
經(jīng)常查看GC監(jiān)控籍胯,如何發(fā)現(xiàn)不正常,需要想辦法對其進行優(yōu)化离福。
對于CMS垃圾收集器杖狼,如果發(fā)現(xiàn)remark過長,如果是大量本地緩存應(yīng)用的話這個過長應(yīng)該很正常妖爷,因為在并發(fā)階段很容易有很多新對象進入緩存蝶涩,從而remark階段掃描很耗時,remark又會暫停絮识÷唐福可以開啟-XX:CMSScavengeBeforeRemark,在remark階段前進行一次YGC次舌,從而減少remark階段掃描gc root的開銷熄攘。
可以使用G1垃圾收集器,通過-XX:MaxGCPauseMillis設(shè)置最大停頓時間垃它,提高服務(wù)可用性鲜屏。
9. 緩存的監(jiān)控
很多人對于緩存的監(jiān)控也比較忽略烹看,基本上線之后如果不報錯然后就默認他就生效了。但是存在這個問題洛史,很多人由于經(jīng)驗不足惯殊,有可能設(shè)置了不恰當?shù)倪^期時間,或者不恰當?shù)木彺娲笮?dǎo)致緩存命中率不高也殖,讓緩存就成為了代碼中的一個裝飾品土思。所以對于緩存各種指標的監(jiān)控,也比較重要忆嗜,通過其不同的指標數(shù)據(jù)己儒,我們可以對緩存的參數(shù)進行優(yōu)化,從而讓緩存達到最優(yōu)化:
上面的代碼中用來記錄get操作的捆毫,通過Cat記錄了獲取緩存成功闪湾,緩存不存在,緩存過期绩卤,緩存失敗(獲取緩存時如果拋出異常途样,則叫失敗),通過這些指標濒憋,我們就能統(tǒng)計出命中率何暇,我們調(diào)整過期時間和大小的時候就可以參考這些指標進行優(yōu)化。
10. 一款好的框架
一個好的劍客沒有一把好劍怎么行呢凛驮?如果要使用好緩存裆站,一個好的框架也必不可少。在最開始使用的時候大家使用緩存都用一些util黔夭,把緩存的邏輯寫在業(yè)務(wù)邏輯中:
上面的代碼把緩存的邏輯耦合在業(yè)務(wù)邏輯當中宏胯,如果我們要增加成多級緩存那就需要修改我們的業(yè)務(wù)邏輯,不符合開閉原則纠修,所以引入一個好的框架是不錯的選擇胳嘲。
推薦大家使用JetCache這款開源框架,其實現(xiàn)了Java緩存規(guī)范JSR107并且支持自動刷新等高級功能扣草。筆者參考JetCache結(jié)合Spring Cache, 監(jiān)控框架Cat以及美團的熔斷限流框架Rhino實現(xiàn)了一套自有的緩存框架了牛,讓操作緩存,打點監(jiān)控辰妙,熔斷降級鹰祸,業(yè)務(wù)人員無需關(guān)心。上面的代碼可以優(yōu)化成:
對于一些監(jiān)控數(shù)據(jù)也能輕松從大盤上看到:
最后
想要真正的使用好一個緩存密浑,必須要掌握很多的知識蛙婴,并不是看幾個Redis原理分析,就能把Redis緩存用得爐火純青尔破。對于不同場景街图,緩存有各自不同的用法浇衬,同樣的不同的緩存也有自己的調(diào)優(yōu)策略,進程內(nèi)緩存你需要關(guān)注的是他的淘汰算法和GC調(diào)優(yōu)餐济,以及要避免緩存污染等耘擂。分布式緩存你需要關(guān)注的是他的高可用,如果其不可用了如何進行降級絮姆,以及一些序列化的問題醉冤。一個好的框架也是必不可少的,對其如果使用得當再加上上面介紹的經(jīng)驗篙悯,相信能讓你很好的駕馭住這頭野馬——緩存蚁阳。如何優(yōu)雅的設(shè)計和使用緩存?
如果你現(xiàn)在在JAVA這條路上,可以來領(lǐng)取Java工程化鸽照、高性能及分布式螺捐、高性能、高架構(gòu)矮燎、性能調(diào)優(yōu)归粉、Spring,MyBatis漏峰,Netty源碼分析和大數(shù)據(jù)等資料。QQ群為:835638062