若是第二次看到個人文章,歡迎文末掃碼訂閱我我的的公衆號(跨界架構師)喲~
本文長度爲 2805字,建議閱讀 8分鐘。
堅持原創,每一篇都是用心之做~
有句話說得好,欲要使其毀滅,先要使其瘋狂。當你沉浸在緩存所帶來的系統tps飆升的喜悅中時,使你係統毀滅的種子也已經埋在其中。html
並且,你所承載的tps越高,它所帶來的毀滅性更大。程序員
在前兩篇《360°全方位解讀「緩存」》和《先寫DB仍是「緩存」?》中,咱們已經對緩存有了必定的認識,而且知道了關於緩存相關的「一致性」問題的最佳實踐。數據庫
此次,咱們就來聊聊隱藏在緩存中的毀滅性種子是什麼?數組
咱們從前一篇文章《先寫DB仍是「緩存」?》中屢次提到的「cache miss」提及。緩存
在前一篇文章《先寫DB仍是「緩存」?》中,咱們屢次提到了「cache miss」這個詞,利用「cache miss」來更好的保障DB和緩存之間的數據一致性。微信
然而,任何事物都是有兩面性的,「cache miss」在提供便利的同時,也帶來了一個潛在風險。架構
這個風險就是「緩存雪崩」。併發
在圖中的第二步,大量的請求併發進入,這裏的一次「cache miss」就有可能致使產生「緩存雪崩」。dom
不過,雖然「cache miss」會產生「緩存雪崩」,但「緩存雪崩」並不只僅產生於「cache miss」。分佈式
雪崩一詞源於「雪崩效應」,是指像「多米勒骨牌」這樣的級聯反應。前面沒頂住,致使影響後面,如此蔓延。(關於對應雪崩的方式參考以前的文章,文末放連接)
因此「緩存雪崩」的根本問題是:緩存因爲某些緣由未起到預期的緩衝效果,致使請求所有流轉到數據庫,形成數據庫壓力太重。
所以,流量激增、高併發下的緩存過時、甚至緩存系統宕機都有可能產生「緩存雪崩」問題。
怎麼解決這個問題呢?宕機能夠經過作高可用來解決(能夠參考以前的文章,文末放連接)。而在「流量激增」、「高併發下的緩存過時」這兩種場景下,也有兩種方式能夠來解決。
經過加鎖或者排隊機制來限制讀數據庫寫緩存的線程數量。好比,下面的僞代碼就是對某個key只容許一個線程進入的效果。
key = "aaa"; var cacheValue = cache.read(key); if (cacheValue != null) { return cacheValue; } else { lock(key) { cacheValue = cache.read(key); if (cacheValue != null) { return cacheValue; } else { cacheValue = db.read(key); cache.set(key,cacheValue); } } return cacheValue; }
這個比較好理解,就不廢話了。
這個主要針對的是「緩存定時過時」機制下的取巧方案。它的目的是避免多個緩存key在同一時間失效,致使壓力更加集中。
好比,你有10個key,他們的過時時間都是30分鐘的話,那麼30分鐘後這10個key的全部請求會同時流到db去。
而這裏說的這種方式就是將這10個key的過時時間打亂,好比設置成2五、2六、2七、...、34分的過時時間,這樣壓力就被分散了,每分鐘只有一個key過時。
最簡單粗暴的方式就是在設置「過時時間」的時候加一個隨機數字。
cache.set(key,cacheValue,30+random())
整體來看,相比後者,前者的適用面更廣,因此Z哥建議你用「加鎖排隊」做爲默認的通用方案不失爲一個不錯的選擇。
若是你據說過「緩存穿透」的話,可能會問:「緩存雪崩」和「緩存穿透」同樣嗎?
從產生的效果上看是同樣的,可是過程不一樣。
來舉個例子。例子純屬虛構,別太在乎合理性~
在一個方圓一萬里的地區內,只有一個修手機的老師傅。他收了一個徒弟,但願徒弟能幫他分擔掉一部分的工做壓力。這裏的老師傅能夠看做是DB,徒弟看做是緩存。老師傅對徒弟說,若是遇到你不會作的事你來請教我。
而後,一個客戶過來講要修一下他的衛星電話,徒弟去請教老師傅,老師傅說他也不會,先拒絕了吧。
可是因爲沒告訴他後續遇到修衛星電話的人該怎麼作,因此後續這個客戶一直來問,徒弟每次都又去請教老師傅。最終,在修衛星電話這件事上,徒弟並無幫老師傅緩解任何的壓力,快被煩死了。
上面這個故事就比如「緩存穿透」。
而「緩存雪崩」則是,因爲徒弟年輕力壯,精力充沛,1小時能修20個手機,老師傅只能修10個(可是手藝好,更考究)。
而後,有一天徒弟請假了,但恰巧這天來了2000個修手機的,老師傅修不過來就被累垮了。
因此,「緩存穿透」和「緩存雪崩」最終產生的效果是同樣的,就是由於大量請求流到DB後,把DB拖垮(正如前面故事中的老師傅)。
二者最大的不一樣在於,「緩存雪崩」問題只要數據從db中找到並放入緩存就能恢復正常(徒弟休假歸來),而「緩存穿透」指的是所需的數據在DB中一直不存在的狀況(老師傅也不會修)。而且,因爲DB中數據不存在,因此天然每次從緩存中也找不到(徒弟也不會修)。
清楚了二者的區別以後,咱們下面就來聊聊「緩存穿透」的常見應對方式。
「緩存穿透」有時也叫作「緩存擊穿」,產生的邏輯過程是這樣,一直在虛線範圍內流轉。
在這種場景下,緩存的做用徹底失效,每次請求都「穿透」到了DB中。
可能你會想,爲何會存在大量的這種db中數據不存在的狀況呢?其實,任何依賴外部參數進行查詢的地方均可能有這個問題的存在。好比,一個文本輸入框,原本是讓你輸入用戶名的,可是手誤輸入了密碼,天然就找不到數據咯。更主要的問題是,會有惡意分子利用這種機制來對你的系統進行攻擊,擊穿緩存搞垮你的數據庫,致使整個系統全面癱瘓。
一樣也有兩種方式來解決這個問題。
布隆過濾器就是由一個很長的二進制向量和一系列隨機映射函數組成,將肯定不存在的數據構建到過濾器中,用它來過濾請求。這裏就放個圖,具體就不展開了,後續咱們再聊(有興趣的能夠先到搜索引擎搜《Space time trade-offs in hash coding with allowable errors》找到bloom的原始論文)。
實現代碼其實並不很複雜,參考論文或者網上其餘做者的一些實現就能夠寫出來。
不過,布隆過濾器有一個最大的缺點,也是其爲了高效利用內存而付出的代價,就是沒法確保100%的準確率。
因此,若是你的場景要求是100%準確的,就只能用下面這種方式了。
其實就是哪怕從db中取出的數據是「空(null)」,也把它丟失到緩存中。
這樣一來,雖然緩存中存在着一個value爲空的數據,可是至少他能表示「數據庫裏也沒有不用找了」。
其實這個思路和布隆過濾器有些相似,可是它對內存的消耗會大不少,畢竟布隆過濾器是利用的bit位來存儲。不過這種方式的優點是前面提到的,不會出現偏差,而布隆過濾器的錯誤率會隨着「位數」的增長而減小,會不斷趨近於0,但不會爲0。
好了,咱們一塊兒總結一下。
此次呢,Z哥主要和你聊了隱藏在緩存中的兩顆具備「毀滅性」的種子,「緩存雪崩」和「緩存穿透」,以及應對這兩顆種子的經常使用方式。
並且,順便幫你區分清楚了「緩存雪崩」和「緩存穿透」的差別。
但願對你有所啓發。
相關文章:
做者:Zachary
出處:https://www.cnblogs.com/Zacha...
若是你喜歡這篇文章,能夠點一下文末的「贊」。
這樣能夠給我一點反饋。: )
謝謝你的舉手之勞。
▶關於做者:張帆(Zachary, 我的微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎 掃描下方的二維碼~。
按期發表原創內容: 架構設計丨分佈式系統丨產品丨運營丨一些思考。若是你是初級程序員,想提高但不知道如何下手。又或者作程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注個人公衆號「跨界架構師」,回覆「技術」,送你一份我長期收集和整理的思惟導圖。
若是你是運營,面對不斷變化的市場一籌莫展。又或者想了解主流的運營策略,以豐富本身的「倉庫」。歡迎關注個人公衆號「跨界架構師」,回覆「運營」,送你一份我長期收集和整理的思惟導圖。