架構(gòu)思維之緩存雪崩的災(zāi)難復(fù)盤(pán)
1 真實(shí)案例
云辦公系統(tǒng)用戶(hù)實(shí)時(shí)信息查詢(xún)功能優(yōu)化發(fā)布之后,系統(tǒng)發(fā)生宕機(jī)事件(系統(tǒng)掛起,頁(yè)面無(wú)法加載)。
1.1 背景
我們IM原有的一個(gè)功能,當(dāng)鼠標(biāo)移動(dòng)到用戶(hù)頭像的時(shí)候,會(huì)顯示出用戶(hù)的基本信息。信息比較簡(jiǎn)單,只包含簡(jiǎn)單的用戶(hù)名、昵稱(chēng)、性別、郵箱、電話(huà)等基本數(shù)據(jù),
這是一個(gè)典型的數(shù)據(jù)查詢(xún),大概過(guò)程如下左側(cè),訪(fǎng)問(wèn)用戶(hù)基本信息的時(shí)候會(huì)先去Redis中查一下,如果不存在,就把大約2W左右的用戶(hù)數(shù)據(jù)一次性取出來(lái),保存在Redis中,因?yàn)橛脩?hù)基本信息在同一張表上,用戶(hù)信息表的數(shù)據(jù)量也很少,所以一直也沒(méi)什么問(wèn)題。
過(guò)程如下圖左側(cè)所示。
后續(xù)對(duì)功能做了優(yōu)化,原有采集的信息除了用戶(hù)的基本信息之外,還采集了教育經(jīng)歷、工作經(jīng)歷、所獲勛章等。
這些信息存儲(chǔ)在不同的表里面,所以采集過(guò)程是一個(gè)復(fù)雜的聯(lián)表查詢(xún),特別是有些基礎(chǔ)表數(shù)據(jù)量比較大,執(zhí)行效率也是比較慢的。
如果把所有用戶(hù)全部取出來(lái)并存儲(chǔ)在一個(gè)Redis節(jié)點(diǎn)中,明顯已經(jīng)不適用,一個(gè)是批量查詢(xún)導(dǎo)致數(shù)據(jù)庫(kù)執(zhí)行效率慢,一個(gè)是Redis單節(jié)點(diǎn)數(shù)據(jù)太大。
所以開(kāi)發(fā)同學(xué)做了下優(yōu)化,每次只取單個(gè)用戶(hù)的綜合信息存在Redis中,一個(gè)用戶(hù)建一個(gè)緩存,如上圖右側(cè)所示。
1.2 問(wèn)題處理
這種做法看著沒(méi)啥問(wèn)題,當(dāng)晚發(fā)布后,在第二天的上午10點(diǎn)~11點(diǎn)就發(fā)生了系統(tǒng)瓶頸卡頓,最后掛起的情況,數(shù)據(jù)庫(kù)的內(nèi)存、CPU全部飆上去了。
第一時(shí)間的處理方法是降級(jí),程序回滾到之前只提供基本信息的階段,其他的前端默認(rèn)顯示空信息。接著就是對(duì)問(wèn)題進(jìn)行分析了,后確認(rèn)原因是產(chǎn)生了 緩存雪崩了。
新發(fā)布的系統(tǒng),緩存池是空的,在早上10點(diǎn)高峰期的時(shí)候,大量的人員到IM上進(jìn)行訪(fǎng)問(wèn),系統(tǒng)開(kāi)始初次建立每個(gè)人的緩存信息,大量的請(qǐng)求查詢(xún)不到緩存,直接透過(guò)緩存池投向數(shù)據(jù)庫(kù),造成瞬時(shí)DB請(qǐng)求量井噴。這是典型的緩存雪崩了。
同時(shí)因?yàn)?,失效時(shí)間相近(8小時(shí)失效),所以也有潛在的緩存雪崩。
應(yīng)急處理方案:適當(dāng)處理緩存的機(jī)制,采用布隆過(guò)濾器、空初始值、隨機(jī)緩存失效時(shí)間方式來(lái)預(yù)防緩存擊穿和緩存雪崩的產(chǎn)生。
最終解決方案:改回原來(lái)緩存全公司員工信息的方式,根據(jù)執(zhí)行計(jì)劃和SlowLog,優(yōu)化獲取員工信息的SQL腳本,去掉不需要的字段和無(wú)意義的連接。
2 緩存雪崩
2.1 概念
緩存雪崩是指大量的key設(shè)置了相同的過(guò)期時(shí)間,導(dǎo)致在緩存在同一時(shí)刻全部失效,造成瞬時(shí)DB請(qǐng)求量大、壓力驟增,引起雪崩。
上面的哪個(gè)問(wèn)題,初次訪(fǎng)問(wèn)的數(shù)據(jù)都是未建立緩存的,跟同時(shí)失效的情況一樣,當(dāng)峰值期到來(lái)的時(shí)候,會(huì)大量的請(qǐng)求查詢(xún)不到緩存,直接透過(guò)緩存池投向數(shù)據(jù)庫(kù),造成瞬時(shí)DB請(qǐng)求量井噴。
2.2 解決方案分析
2.2.1 緩存集群+數(shù)據(jù)庫(kù)集群
在系統(tǒng)容量設(shè)計(jì)的時(shí)候,應(yīng)該能夠預(yù)見(jiàn)后期會(huì)有大量的請(qǐng)求,所以在發(fā)生雪崩前對(duì)緩存集群實(shí)現(xiàn)高可用,如果是使用 Redis,可以使用 主從+哨兵 ,Redis Cluster 來(lái)避免 Redis 全盤(pán)崩潰的情況。
同樣的,也需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行高可用保障,因?yàn)橥高^(guò)緩存之后,真正考驗(yàn)的是數(shù)據(jù)庫(kù)的抗壓能力。所以 1主N從 甚至 數(shù)據(jù)庫(kù)集群 是我們需要重點(diǎn)去考慮的。
2.2.2 適當(dāng)?shù)南蘖?、降?jí)
可以使用 Hystrix進(jìn)行限流 + 降級(jí) ,比如像上面那種情況,一下子來(lái)了1W個(gè)請(qǐng)求,不是當(dāng)前系統(tǒng)的吞吐能力能夠承受的,假設(shè)單秒TPS的能力只能是 5000個(gè),那么剩余的 5000 請(qǐng)求就可以走限流邏輯。
可以設(shè)置一些默認(rèn)值,然后調(diào)用我們自己降級(jí)邏輯去FallBack,保護(hù)最后的 MySQL 不會(huì)被大量的請(qǐng)求掛起。 除了Hystrix之外,阿里的Sentinel 和 Google的RateLimiter 都是不錯(cuò)的選擇。
Sentinel 漏桶算法
RateLimiter 令牌桶算法
另外可以考慮使用用本地緩存來(lái)進(jìn)行緩沖,在 Redis Cluster 不可用的時(shí)候,不至于全線(xiàn)崩潰。
2.2.3 隨機(jī)過(guò)期時(shí)間
可以給緩存設(shè)置過(guò)期時(shí)間時(shí)加上一個(gè)隨機(jī)值時(shí)間,使得每個(gè)key的過(guò)期時(shí)間分布開(kāi)來(lái),不會(huì)集中在同一時(shí)刻失效。
隨機(jī)值我們團(tuán)隊(duì)的做法是:n * 3/4 + n * random() 。所以,比如你原本計(jì)劃對(duì)一個(gè)緩存建立的過(guò)期時(shí)間為8小時(shí),那就是6小時(shí) + 0~2小時(shí)的隨機(jī)值。
這樣保證了均勻分布在 6~8小時(shí)之間。如圖:
2.2.4 緩存預(yù)熱
類(lèi)似上面的那個(gè)案例,并不是還沒(méi)過(guò)期,而是新功能發(fā)布,壓根還沒(méi)建設(shè)過(guò)緩存,所以可以在峰值期之前先做好部分緩存,避免瞬時(shí)壓力太大。
所以如果10點(diǎn)是峰值期,那么可以預(yù)先在8~10點(diǎn)期間,可以逐漸的把大部分緩存建立起來(lái)。如圖:
3 緩存穿透
3.1 概念
緩存穿透是指訪(fǎng)問(wèn)一個(gè)不存在的key,緩存不起作用,請(qǐng)求會(huì)穿透到DB,流量井噴時(shí)會(huì)導(dǎo)致DB掛掉。
比如 我們查詢(xún)用戶(hù)的信息,程序會(huì)根據(jù)用戶(hù)的編號(hào)去緩存中檢索,如果找不到,再到數(shù)據(jù)庫(kù)中搜索。如果你給了一個(gè)不存在的編號(hào):XXXXXXXX,那么每次都比對(duì)不到,就透過(guò)緩存進(jìn)入數(shù)據(jù)庫(kù)。
這樣風(fēng)險(xiǎn)很大,如果因?yàn)槟承┰驅(qū)е麓罅坎淮嬖诘木幪?hào)被查詢(xún),甚至被惡意偽造編號(hào)進(jìn)行攻擊,那將是災(zāi)難。
3.2 解決方案分析
3.2.1 緩存空值
發(fā)生穿透的原因是緩存中沒(méi)有存儲(chǔ)這些空數(shù)據(jù)的key,或者壓根這個(gè)數(shù)據(jù)的key是不會(huì)存在的,從而導(dǎo)致每次查詢(xún)都進(jìn)入數(shù)據(jù)庫(kù)中。
我們就可以將這些key的值設(shè)置為null,并寫(xiě)到緩存池中。后面再出現(xiàn)查詢(xún)這個(gè)key 的請(qǐng)求的時(shí)候,直接返回null,這樣就在緩存池中就被判斷返回了,壓力在緩存層中,不會(huì)轉(zhuǎn)移到數(shù)據(jù)庫(kù)上。
3.2.2 BloomFilter
我們稱(chēng)作布隆過(guò)濾器,BloomFilter 類(lèi)似于一個(gè)hbase set 用來(lái)判斷某個(gè)元素(key)是否存在于某個(gè)集合中。
這種方式在大數(shù)據(jù)場(chǎng)景應(yīng)用比較多,比如 Hbase 中使用它去判斷數(shù)據(jù)是否在磁盤(pán)上。還有在爬蟲(chóng)場(chǎng)景判斷url 是否已經(jīng)被爬取過(guò)。
這種方案可以加在第一種方案中,在緩存之前在加一層 BloomFilter ,把存在的key記錄在BloomFilter中,在查詢(xún)的時(shí)候先去 BloomFilter 去查詢(xún) key 是否存在,如果不存在就直接返回,存在再走查緩存 ,投入數(shù)據(jù)庫(kù)去查詢(xún),這樣減輕了數(shù)據(jù)庫(kù)的壓力。
流程圖如下:
3.2.3 兩種方案的選擇判斷
前面說(shuō)過(guò),可能會(huì)存在一些惡意攻擊,偽造出大量不存在的key ,這種情況下如果我們?nèi)绻捎镁彺婵罩档霓k法,就會(huì)產(chǎn)生大量不存在key的null數(shù)據(jù)。顯然是不合適的,這時(shí)我們完全可以使用第二種方案進(jìn)行過(guò)濾掉這些key。
所以,判斷的依據(jù)是:
針對(duì)key非常多、請(qǐng)求重復(fù)率比較低的數(shù)據(jù),我們就沒(méi)有必要進(jìn)行緩存,使用 BloomFilter 直接過(guò)濾掉。
而對(duì)于空數(shù)據(jù)的key有限的,重復(fù)率比較高的,我們則可以采用 緩存空值的辦法 進(jìn)行處理。
4 緩存擊穿
4.1 概念
一個(gè)存在的key,在緩存過(guò)期的一刻,同時(shí)有大量的請(qǐng)求,這些請(qǐng)求都會(huì)擊穿到DB,造成瞬時(shí)DB請(qǐng)求量大、壓力驟增。(注意跟上面兩種的區(qū)別)
4.2 解決方案
4.2.1 鎖的方式
分布式鎖場(chǎng)景,在訪(fǎng)問(wèn)key之前,采用SETNX(set if not exists)來(lái)設(shè)置另一個(gè)短期key來(lái)鎖住當(dāng)前key的訪(fǎng)問(wèn),訪(fǎng)問(wèn)結(jié)束再刪除該短期key。
這種現(xiàn)象是多個(gè)線(xiàn)程同時(shí)去查詢(xún)數(shù)據(jù)庫(kù)的這條數(shù)據(jù),那么我們可以在第一個(gè)查詢(xún)數(shù)據(jù)的請(qǐng)求上使用一個(gè) 互斥鎖來(lái)鎖住它。
其他的線(xiàn)程走到這一步拿不到鎖就等著,等第一個(gè)線(xiàn)程查詢(xún)到了數(shù)據(jù),然后做緩存。后面的線(xiàn)程進(jìn)來(lái)發(fā)現(xiàn)已經(jīng)有緩存了,就直接走緩存。
鎖不好的地方就是在其他線(xiàn)程在拿不到鎖的時(shí)候就等待,這個(gè)會(huì)造成系統(tǒng)整體吞吐量降低,用戶(hù)體驗(yàn)度也不好。
4.2.2 空初始值
這是一種短暫降級(jí)的方式:
如果一個(gè)緩存失效的時(shí)候,有無(wú)數(shù)個(gè)請(qǐng)求狂奔而來(lái),而第一個(gè)請(qǐng)求從進(jìn)入緩存池,判空,再到數(shù)據(jù)庫(kù)檢索,再查詢(xún)出結(jié)果并返回設(shè)置緩存的這個(gè)過(guò)程里,緩存是不存在的。
這個(gè)就很危險(xiǎn),超高并發(fā)下這個(gè)短暫的過(guò)程足已讓千千萬(wàn)萬(wàn)請(qǐng)求投向數(shù)據(jù)庫(kù)。更別提這可能是個(gè)慢查詢(xún),整個(gè)過(guò)程可能長(zhǎng)達(dá)2s以上,那對(duì)數(shù)據(jù)庫(kù)是一種非常大的傷害。
業(yè)內(nèi)有一種做法叫做空初始值,短暫的局部降級(jí)來(lái)保證整個(gè)數(shù)據(jù)庫(kù)系統(tǒng)不被擊穿。大概流程如下:
可以看出,整個(gè)過(guò)程中我們犧牲了A、B、C、D的請(qǐng)求,他們拿回了一個(gè)空值或者默認(rèn)值,但是這局部的降級(jí)卻保證整個(gè)數(shù)據(jù)庫(kù)系統(tǒng)不被擁堵的請(qǐng)求擊穿。
這也是我面試中最喜歡問(wèn)候選人的緩存類(lèi)問(wèn)題。
以上就是架構(gòu)思維之緩存雪崩的災(zāi)難復(fù)盤(pán)的詳細(xì)內(nèi)容,更多關(guān)于緩存雪崩災(zāi)難的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MAC系統(tǒng)IDEA顏值插件MaterialThemeUI
俗話(huà)說(shuō),工欲善其事必先利其器。工具的顏值也很重要,好的主題讓人賞心悅目,有碼代碼的欲望。今天推薦一個(gè)IDEA顏值類(lèi)插件:Material Theme UI2021-09-09Git基礎(chǔ)學(xué)習(xí)之分支基本操作詳解
這篇文章主要為大家詳細(xì)介紹了Git基礎(chǔ)學(xué)習(xí)中分支的基本操作,例如分支的創(chuàng)建、查看、切換和刪除等,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-10-10如何在網(wǎng)頁(yè)中顯示服務(wù)器時(shí)間
在網(wǎng)頁(yè)上顯示時(shí)間,如果取的是用戶(hù)本機(jī)的時(shí)間,由于用戶(hù)的時(shí)間往往不準(zhǔn)確,所以顯示的有問(wèn)題。而服務(wù)器時(shí)間一般不會(huì)誤差太大,所以最好顯示服務(wù)器時(shí)間2013-03-03五步完成unity與微信(游戲)小程序交互創(chuàng)建視頻
這篇文章主要介紹了unity與微信(游戲)小程序交互創(chuàng)建視頻的步驟,非常簡(jiǎn)單,只需要五步就可完成,有需要的朋友可以借鑒參考下,希望可以有所幫助2021-09-09Git pull(拉取)及push(上傳)相關(guān)命令介紹
這篇文章主要介紹了Git pull(拉取),push(上傳)相關(guān)命令,git是一個(gè)非常好用的分布式版本管理工具,Git是去中心化,每一個(gè)分支都是一個(gè)中心,并且支持本地倉(cāng)庫(kù)存儲(chǔ),像如今很多大公司都用git做版本控制。有興趣的話(huà)來(lái)學(xué)習(xí)一下2020-07-07解決Git?Revert?再次合代碼無(wú)效問(wèn)題
這篇文章主要為大家介紹了解決Git?Revert?再次合代碼無(wú)效問(wèn)題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08