緩存的設計與使用

一蓄拣、引言

本文談及的是后臺業(yè)務服務緩存問題扬虚,在構建和優(yōu)化業(yè)務服務時,第一想到的應該是優(yōu)化數(shù)據(jù)庫球恤,比如數(shù)據(jù)庫模型設計辜昵、SQL結構化查詢語句優(yōu)化,慢查詢往往是系統(tǒng)性能殺手咽斧;第二便是使用緩存堪置。在應用中使用緩存的技術手段并不新鮮,計算機組成原理中我們已經學習過张惹,早期的計算機CPU通過fsb(中央處理器數(shù)據(jù)總線)直接與主存(內存)連接的方式導致CPU吞吐率下降舀锨,內存成為性能瓶頸,同時又由于內存訪問熱點數(shù)據(jù)的集中性宛逗,誕生了位于CPU和內存之間的高速緩存坎匿。這類比于高并發(fā)的場景,數(shù)據(jù)庫讀庫性能成為瓶頸雷激,需要在DB和高并發(fā)的調用之間加上緩存替蔬。緩存所帶來的性能提升效果更直接、高效屎暇。但是相比其他優(yōu)化手段承桥,緩存的使用并不是零成本的,任何系統(tǒng)使用緩存根悼,都會遇到兩大問題:數(shù)據(jù)不一致問題和系統(tǒng)復雜度增加凶异。深入業(yè)務蜀撑,才能有好的設計。在設計緩存的時候唠帝,要根據(jù)我們的業(yè)務特點屯掖,明白什么是不容易變的玄柏,什么是相對容易變襟衰,什么是真正觸達用戶的,哪些數(shù)據(jù)是關鍵粪摘。這樣才能發(fā)現(xiàn)真正應該cache的點瀑晒。

二、緩存設計與使用

衡量緩存設計好壞的衡量指標是緩存命中率徘意,緩存的命中率=緩存命中次數(shù)/請求次數(shù)苔悦,命中率越高,緩存的使用率越高椎咧。緩存的適用場景總的來說是混存訪問高頻讀低頻寫的數(shù)據(jù)玖详。相反的對數(shù)據(jù)要求苛刻,變化頻率高勤讽、訪問頻率低的數(shù)據(jù)不能緩存蟋座。設計和使用緩存首先要了解服務、DB脚牍、Cache的性能情況向臀,一般情況下,DB的響應是10ms級(優(yōu)化充足的情況下诸狭,SQL平均耗時1ms券膀。這是因為命中了索引,并且命中了MySQL緩沖池(內存中)驯遇。如果命中索引但不命中緩沖池芹彬,且查詢數(shù)據(jù)量不大,磁盤并發(fā)量不高叉庐,則大約耗時10ms(磁盤尋道)雀监。如果這些都不滿足,耗時就大了)眨唬,分布式緩存在ms級会前,進程內緩存在ns級。圖1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖1 緩存響應時間

本地緩存可用hashmap(注意并發(fā))匾竿、guava-cache(推薦)等實現(xiàn)瓦宜。對于一致性要求不高且緩存命中率較高的數(shù)據(jù)服務,本地緩存可以減少服務端調用次數(shù)岭妖;集中緩存又稱分布式緩存临庇,實現(xiàn)由redis(推薦)反璃、memcache等。

2.1 分布式緩存的設計與使用

緩存的設計模式一般分為四種Cache aside, Read through, Write through, Write behind caching假夺。

2.1.1 Cache Aside Pattern

Cache aside pattern是最常用的方式淮蜈,其具體邏輯如下:

失效:應用程序先從cache取數(shù)據(jù),沒有得到已卷,則從數(shù)據(jù)庫中取數(shù)據(jù)梧田,成功后,放到緩存中侧蘸。

命中:應用程序從cache中取數(shù)據(jù)裁眯,取到后返回。

更新:先把數(shù)據(jù)存到數(shù)據(jù)庫中讳癌,成功后穿稳,再讓緩存失效。


這是標準的design pattern晌坤,包括Facebook的論文《Scaling Memcache at Facebook》也使用了這個策略逢艘。為什么不是寫完數(shù)據(jù)庫后更新緩存?你可以看一下Quora上的這個問答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》骤菠,主要是怕兩個并發(fā)的寫操作導致臟數(shù)據(jù)它改。

那么,是不是Cache Aside這個就不會有并發(fā)問題了娩怎?不是的搔课,比如,一個是讀操作截亦,但是沒有命中緩存爬泥,然后就到數(shù)據(jù)庫中取數(shù)據(jù),此時來了一個寫操作崩瓤,寫完數(shù)據(jù)庫后袍啡,讓緩存失效,然后却桶,之前的那個讀操作再把老的數(shù)據(jù)放進去境输,這時會造成臟數(shù)據(jù)。

但颖系,這個case理論上會出現(xiàn)嗅剖,不過,實際上出現(xiàn)的概率可能非常低嘁扼,因為這個條件需要發(fā)生在讀緩存時緩存失效信粮,而且并發(fā)著有一個寫操作。而實際上數(shù)據(jù)庫的寫操作會比讀操作慢得多趁啸,而且還要鎖表强缘,而讀操作必需在寫操作前進入數(shù)據(jù)庫操作督惰,而又要晚于寫操作更新緩存,所有的這些條件都具備的概率基本并不大旅掂。

所以赏胚,這也就是Quora上的那個答案里說的,要么通過2PC或是Paxos協(xié)議保證一致性商虐,要么就是拼命的降低并發(fā)時臟數(shù)據(jù)的概率觉阅,而Facebook使用了這個降低概率的玩法,因為2PC太慢称龙,而Paxos太復雜留拾。當然戳晌,最好還是為緩存設置上過期時間鲫尊。

2.1.2Read/Write Through Pattern

我們可以看到,在上面的Cache Aside套路中沦偎,我們的應用代碼需要維護兩個數(shù)據(jù)存儲疫向,一個是緩存(Cache),一個是數(shù)據(jù)庫(Repository)豪嚎。所以搔驼,應用程序比較啰嗦。而Read/Write Through套路是把更新數(shù)據(jù)庫(Repository)的操作由緩存自己代理了侈询,所以舌涨,對于應用層來說,就簡單很多了扔字∧壹危可以理解為,應用認為后端就是一個單一的存儲革为,而存儲自己維護自己的Cache扭粱。

Read Through

Read Through 套路就是在查詢操作中更新緩存,也就是說震檩,當緩存失效的時候(過期或LRU換出)琢蛤,Cache Aside是由調用方負責把數(shù)據(jù)加載入緩存,而Read Through則用緩存服務自己來加載抛虏,從而對應用方是透明的博其。

Write Through

Write Through 套路和Read Through相仿,不過是在更新數(shù)據(jù)時發(fā)生迂猴。當有數(shù)據(jù)更新的時候慕淡,如果沒有命中緩存,直接更新數(shù)據(jù)庫错忱,然后返回儡率。如果命中了緩存挂据,則更新緩存,然后再由Cache自己更新數(shù)據(jù)庫(這是一個同步操作)

下圖自來Wikipedia的Cache詞條儿普。其中的Memory你可以理解為就是我們例子里的數(shù)據(jù)庫崎逃。


2.1.3Write Behind Caching Pattern

Write Behind 又叫 Write Back。一些了解Linux操作系統(tǒng)內核的同學對write back應該非常熟悉眉孩,這不就是Linux文件系統(tǒng)的Page Cache的算法嗎个绍?是的,你看基礎這玩意全都是相通的浪汪。在更新數(shù)據(jù)的時候巴柿,只更新緩存,不更新數(shù)據(jù)庫死遭,而我們的緩存會異步地批量更新數(shù)據(jù)庫广恢。這個設計的好處就是讓數(shù)據(jù)的I/O操作飛快無比(因為直接操作內存嘛 ),因為異步呀潭,write back還可以合并對同一個數(shù)據(jù)的多次操作钉迷,所以性能的提高是相當可觀的。

但是钠署,其帶來的問題是糠聪,數(shù)據(jù)不是強一致性的,而且可能會丟失(我們知道Unix/Linux非正常關機會導致數(shù)據(jù)丟失谐鼎,就是因為這個事)舰蟆。在軟件設計上,我們基本上不可能做出一個沒有缺陷的設計狸棍,就像算法設計中的時間換空間身害,空間換時間一個道理,有時候隔缀,強一致性和高性能题造,高可用和高性性是有沖突的。軟件設計從來都是取舍Trade-Off猾瘸。

另外界赔,Write Back實現(xiàn)邏輯比較復雜,因為他需要track有哪數(shù)據(jù)是被更新了的牵触,需要刷到持久層上淮悼。操作系統(tǒng)的write back會在僅當這個cache需要失效的時候,才會被真正持久起來揽思,比如袜腥,內存不夠了,或是進程退出了等情況钉汗,這又叫l(wèi)azy write羹令。

在wikipedia上有一張write back的流程圖鲤屡,基本邏輯如下:


2.2 使用本地緩存

本地緩存是應用程序在同一進程內的緩存,通常是ConcurrentHashMap福侈。優(yōu)點是耗時低得忽略不計酒来,缺點是占用本地內存、多機會冗余肪凛、數(shù)據(jù)不同步堰汉。當集群里每個App Server都有個緩存,會有很多數(shù)據(jù)是重復的伟墙,而且某臺機器更新了一條數(shù)據(jù)翘鸭,別的機器不知道,給應用帶來了額外同步的工作戳葵。

同步問題可以用消息隊列來解決就乓,一臺有更新,廣播消息給其他機器譬淳,準實時同步档址。

同步問題也可以用Redis 發(fā)布訂閱模式來通知其它機器進行同步

同步問題也可以用etcd的事件機制來解決多機同步問題

緩存除了加快訪問以外盹兢,也能提高負載能力邻梆。因為數(shù)據(jù)庫連接是有限的資源——不支持NIO(none block IO),每個連接同時只能服務一個線程绎秒,有多少連接就有多少并發(fā)浦妄。如果有慢查詢占住了連接,系統(tǒng)性能會急劇下降见芹!緩存就能減輕對數(shù)據(jù)庫連接的依賴剂娄。

本地緩存和分布式緩存各有千秋,一般建議用一致性更高的分布式緩存玄呛,當性能需要極端調優(yōu)時阅懦,使用本地緩存,或者整體緩存數(shù)據(jù)量不是很大徘铝,具有高頻訪問幾乎不修改的特點的耳胎,建議本地緩存,典型的如業(yè)務的標簽功能惕它。

三怕午、緩存淘汰策略

在只需要緩存一部分熱點數(shù)據(jù)的場景下,比如直播平臺的熱門房間淹魄,需要設計緩存淘汰策略郁惜。

3.1 FIFO(First In, First Out)

即先進先出,淘汰最早進來的混存數(shù)據(jù)甲锡,使用一個標準的隊列即可

3.2 LRU(Least RecentlyUsed)

即最近最少使用兆蕉,淘汰最近不使用的緩存數(shù)據(jù)羽戒。即按照最后被使用時間淘汰

和FIFO不同的是,需要對鏈表做基本模型虎韵,讀寫的時間復雜度是O(1)半醉,寫入新數(shù)據(jù)進入頭部,鏈表滿了數(shù)據(jù)從尾部淘汰;

最近時間被訪問的數(shù)據(jù)移動到頭部劝术,實現(xiàn)算法有很多缩多,如hashmap+雙向鏈表等等;

問題在于若是偶發(fā)性某些key被最近頻繁訪問,而非常態(tài)养晋,則數(shù)據(jù)受到污染衬吆。

2.LFU(Least Frequently used)

即最近使用次數(shù)最少的數(shù)據(jù)被淘汰,注意和LRU的區(qū)別在于LRU的淘汰規(guī)則是基于訪問時間绳泉。LFU按照使用次數(shù)淘汰數(shù)據(jù)

LFU中的每個數(shù)據(jù)塊都有一個引用計數(shù)逊抡,數(shù)據(jù)塊按照引用計數(shù)排序,若是恰好具有相同引用計數(shù)的數(shù)據(jù)塊則按照時間排序;

因為新加入的數(shù)據(jù)訪問次數(shù)為1零酪,所以插入到隊列尾部;

隊列中的數(shù)據(jù)被新訪問后冒嫡,引用計數(shù)增加,隊列重新排序;

當需要淘汰數(shù)據(jù)時四苇,將已經排序的列表最后的數(shù)據(jù)塊刪除;

有很明顯問題是若短時間內被頻繁訪問多次孝凌,比如訪問異常或者循環(huán)沒有控制住月腋,而后很長時間未使用蟀架,則此數(shù)據(jù)會因為頻率高而被錯誤的保留下來,沒有被淘汰榆骚。尤其對于新來的數(shù)據(jù)片拍,由于其起始的次數(shù)是1,所以即便被正常使用也會因為比不過老的數(shù)據(jù)而被淘汰妓肢。所以維基百科說純粹的LFU算法不經常單獨使用而是組合在其他策略中使用捌省。

四、緩存引入的問題

緩存系統(tǒng)一定程度上極大提升系統(tǒng)并發(fā)能力碉钠,但同樣也增加額外技術考慮因素纲缓,比如緩存雪崩、緩存穿透放钦、緩存更新與數(shù)據(jù)一致性

4.1色徘、緩存穿透

緩存穿透是指查詢的key不存在,從而緩存查詢不到而查詢了數(shù)據(jù)庫操禀。若是這樣的key恰好并發(fā)請求很大褂策,那么就會對數(shù)據(jù)庫造成不必要的壓力。比如黑客用一堆不存在的key訪問數(shù)據(jù),大量請求發(fā)送到數(shù)據(jù)庫斤寂,數(shù)據(jù)庫壓力過大而宕機耿焊,怎么解決呢?

1、把所有存在的key都存到另外一個存儲的Set集合里遍搞,查詢時可以先查詢key是否存在;

2罗侯、將這些key對應的值設置為null 丟到緩存里面去。后面再出現(xiàn)查詢這個key 的請求的時候溪猿,直接返回null钩杰,再根據(jù)業(yè)務需求設置過期時間。

3诊县、BloomFilter 類似于一個hbase set 用來判斷某個元素(key)是否存在于某個集合中讲弄。這種方式在大數(shù)據(jù)場景應用比較多,比如 Hbase 中使用它去判斷數(shù)據(jù)是否在磁盤上依痊。這種方案可以加在第1/2種方案中避除,在緩存之前加一層 BloomFilter ,在查詢的時候先去 BloomFilter 去查詢 key 是否存在胸嘁,如果不存在就直接返回瓶摆,存在再走查緩存 -> 查 DB。

4.2性宏、緩存擊穿

在高并發(fā)的系統(tǒng)中群井,大量的請求同時查詢一個 key 時,這個key剛好失效了衔沼,就會導致大量的請求都打到數(shù)據(jù)庫上面去蝌借。這種現(xiàn)象我們稱為緩存擊穿。造成緩存擊穿的原因是多個線程同時去查詢數(shù)據(jù)庫指蚁,那么我們可以在第一個查詢數(shù)據(jù)的請求上使用一個 互斥鎖。其他的線程進入等待狀態(tài)自晰,等第一個線程查詢到了數(shù)據(jù)凝化,然后做緩存。后面的線程進來發(fā)現(xiàn)已經有緩存了酬荞,就直接走緩存搓劫。

4.3、緩存雪崩

定義:緩存雪崩是指緩存系統(tǒng)失效混巧,導致大量請求同時進行數(shù)據(jù)回源枪向,導致數(shù)據(jù)源壓力驟增而崩潰。兩種情況會導致此問題:1咧党、多個緩存數(shù)據(jù)同時失效秘蛔;2、緩存系統(tǒng)崩潰,緩存同時失效

針對多個緩存數(shù)據(jù)同時失效的問題深员,可以將緩存時間離散化负蠕,根據(jù)緩存數(shù)據(jù)訪問規(guī)律和緩存數(shù)據(jù)不一致的敏感性要求來選擇緩存時間。

如果是第二個問題倦畅,緩存系統(tǒng)整體故障遮糖,則整個緩存系統(tǒng)不可用,大量回源請求叠赐,且由于緩存系統(tǒng)故障無法回寫緩存欲账,導致無法快速恢復。

這是緩存系統(tǒng)的引入芭概,在解決高性能敬惦、高并發(fā)的同時,引入新的故障點谈山。

考慮此問題俄删,應從事前、事故中奏路、事后不同階段考慮:

事前:增加緩存系統(tǒng)高可用方案設計畴椰,避免出現(xiàn)系統(tǒng)性故障

事故中:增加多級緩存,在單一緩存故障時鸽粉,仍有其他緩存系統(tǒng)可用斜脂,如之前項目中使用的三級緩存方案:內存級緩存->Memcached->Redis這樣的方案;啟用熔斷限流機制触机,只允許可承受流量帚戳,避免全部流量壓垮系統(tǒng)

事后:緩存數(shù)據(jù)持久化,在故障后快速恢復緩存系統(tǒng)

五儡首、總結

使用緩存會增加系統(tǒng)復雜性片任,應根據(jù)實際業(yè)務考慮是否需要使用以及使用分布式緩存還是本地緩存,設置合理的緩存策略可以提高系統(tǒng)的性能蔬胯,同時要合理的規(guī)避可能由于引入緩存而增加的風險对供。

參考文章:

https://juejin.im/post/5c9a67ac6fb9a070cb24bf34

https://fivezh.github.io/2019/02/11/cache-things/

https://segmentfault.com/a/1190000003858392

http://m.2cto.com/kf/201608/541332.html

http://www.reibang.com/p/46759c5dc5b1

http://www.reibang.com/p/c596e1050954

http://www.reibang.com/p/c596e1050954

http://www.reibang.com/p/c596e1050954

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市氛濒,隨后出現(xiàn)的幾起案子产场,更是在濱河造成了極大的恐慌,老刑警劉巖舞竿,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件京景,死亡現(xiàn)場離奇詭異,居然都是意外死亡骗奖,警方通過查閱死者的電腦和手機确徙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門醒串,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人米愿,你說我怎么就攤上這事厦凤。” “怎么了育苟?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵较鼓,是天一觀的道長。 經常有香客問我违柏,道長博烂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任漱竖,我火速辦了婚禮禽篱,結果婚禮上,老公的妹妹穿的比我還像新娘馍惹。我一直安慰自己躺率,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布万矾。 她就那樣靜靜地躺著悼吱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪良狈。 梳的紋絲不亂的頭發(fā)上后添,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音薪丁,去河邊找鬼遇西。 笑死,一個胖子當著我的面吹牛严嗜,可吹牛的內容都是我干的粱檀。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼阻问,長吁一口氣:“原來是場噩夢啊……” “哼梧税!你這毒婦竟也來了?” 一聲冷哼從身側響起称近,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哮塞,沒想到半個月后刨秆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡忆畅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年衡未,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡缓醋,死狀恐怖如失,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情送粱,我是刑警寧澤褪贵,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站抗俄,受9級特大地震影響脆丁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜动雹,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一槽卫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胰蝠,春花似錦歼培、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翔横,卻和暖如春读跷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禾唁。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工效览, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荡短。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓丐枉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掘托。 傳聞我的和親對象是個殘疾皇子瘦锹,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • 理論總結 它要解決什么樣的問題? 數(shù)據(jù)的訪問闪盔、存取弯院、計算太慢、太不穩(wěn)定泪掀、太消耗資源听绳,同時,這樣的操作存在重復性异赫。因...
    jiangmo閱讀 2,838評論 0 11
  • --- layout: post title: "如果有人問你關系型數(shù)據(jù)庫的原理椅挣,叫他看這篇文章(轉)" date...
    藍墜星閱讀 777評論 0 3
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當在唯一索引所對應的列上鍵入重復值時头岔,會觸發(fā)此異常。 O...
    我想起個好名字閱讀 5,182評論 0 9
  • 本文轉載自https://blog.csdn.net/tTU1EvLDeLFq5btqiK/article/det...
    執(zhí)壹閱讀 241評論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,089評論 1 32