緩存沒預(yù)熱,翻車了!
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競爭對(duì)手一起精進(jìn)!
編輯:業(yè)余草
來源:juejin.cn/post/7277461864349777972
推薦:https://t.zsxq.com/12FEd8lJL
自律才能自由
今天微信群,一位網(wǎng)友分享了他年輕時(shí)候的故事,緩存未預(yù)熱,翻車了。
下面統(tǒng)一使用第一人稱我來描述這個(gè)故事。
緩存不預(yù)熱會(huì)怎么樣?我群友大家淌了路。緩存不預(yù)熱會(huì)導(dǎo)致系統(tǒng)接口性能下降,數(shù)據(jù)庫壓力增加,更重要的是導(dǎo)致我寫了兩天的復(fù)盤文檔,在復(fù)盤會(huì)上被罵出了翔。
悲慘的上線時(shí)刻
事情發(fā)生在幾年前,我剛畢業(yè)時(shí),第一次使用緩存內(nèi)心很激動(dòng)。需求場景是虛擬商品頁面需要向用戶透出庫存狀態(tài),提單時(shí)也需要校驗(yàn)庫存狀態(tài)是否可售賣。但是由于庫存狀態(tài)的計(jì)算包含較復(fù)雜的業(yè)務(wù)邏輯,耗時(shí)比較高,在500ms以上。如果要在商品頁面透出庫存狀態(tài)那么商品頁面耗時(shí)增加500ms,這幾乎是無法忍受的事情。
如何實(shí)現(xiàn)呢?最合適的方案當(dāng)然是緩存了,我當(dāng)時(shí)設(shè)計(jì)的方案是如果緩存有庫存狀態(tài)直接讀緩存,如果緩存查不到,則計(jì)算庫存狀態(tài),然后加載進(jìn)緩存,同時(shí)設(shè)定過期時(shí)間。何時(shí)寫庫存呢?答案是過期后,cache miss時(shí)重新加載進(jìn)緩存。由于計(jì)算邏輯較復(fù)雜,庫存扣減等用戶寫操作沒有同步更新緩存,但是產(chǎn)品認(rèn)可庫存狀態(tài)可以有幾分鐘的狀態(tài)不一致。為什么呢?
因?yàn)閭}庫有冗余庫存,就算庫存狀態(tài)不一致導(dǎo)致超賣,也能容忍。同時(shí)庫存不足以后,需要運(yùn)營補(bǔ)充庫存,而補(bǔ)充庫存的時(shí)間是肯定比較長的。雖然補(bǔ)充庫存完成幾分鐘后,才變?yōu)榭墒圪u的,產(chǎn)品也能接受。梳理完緩存的讀寫方案,我就沉浸于學(xué)習(xí)Redis的過程。
第一次使用緩存,我把時(shí)間和精力都放在Redis存儲(chǔ)結(jié)構(gòu),Redis命令,Redis為什么那么快等方面的關(guān)注。如饑似渴的學(xué)習(xí)Redis知識(shí)。
直到上線階段我也沒有意識(shí)到系統(tǒng)設(shè)計(jì)的缺陷。
代碼寫的很快,測(cè)試驗(yàn)證也沒有問題。然而上線過程中,就開始噼里啪啦的報(bào)警,開始我并沒有想到報(bào)警這事和我有關(guān)。直到有人問我,“XXX,你是不是在上線庫存狀態(tài)的需求?”。
我人麻了,”怎么了,啥事”,我顫抖的問
“商品頁面耗時(shí)暴漲,趕緊回滾”。一個(gè)聲音傳來
“我草”,那一瞬間,我的血壓上涌,手心發(fā)癢,心跳加速,頭皮發(fā)麻,顫抖的手不知道怎么在發(fā)布系統(tǒng)點(diǎn)回滾,“我沒回滾過啊,咋回滾啊?”
“有降級(jí)開關(guān)嗎”? 一個(gè)聲音傳來。
"沒寫..."。我回答的時(shí)候覺得自己真是二筆,為啥沒加降級(jí)啊。(這也是復(fù)盤被罵的重要原因)
那么如何對(duì)緩存進(jìn)行預(yù)熱呢?
如何預(yù)熱緩存
灰度放量
灰度放量實(shí)際上并不是緩存預(yù)熱的辦法,但是確實(shí)能避免緩存雪崩的問題。例如這個(gè)需求場景中,如果我沒有放開全量數(shù)據(jù),而是選擇放量1%的流量。這樣系統(tǒng)的性能不會(huì)有較大的下降,并且逐步放量到100%。
雖然這個(gè)過程中,沒有主動(dòng)同步數(shù)據(jù)到緩存,但是通過控制放量的節(jié)奏,保證了初始化緩存過程中,不會(huì)出現(xiàn)較大的耗時(shí)波動(dòng)。
例如新上線的緩存邏輯,可以考慮逐漸灰度放量。
掃描數(shù)據(jù)庫刷緩存
如果緩存維度是商品維度或者用戶維度,可以考慮掃描數(shù)據(jù)庫,提前預(yù)熱部分?jǐn)?shù)據(jù)到緩存中。
開發(fā)成本較高。除了開發(fā)緩存部分的代碼,還需要開發(fā)掃描全表的任務(wù)。為了控制緩存刷新的進(jìn)度,還需要使用線程池增加并發(fā),使用限流器限制并發(fā)。這個(gè)方案的開發(fā)成本較高。
通過數(shù)據(jù)平臺(tái)刷緩存
這是比較好的方式,具體怎么實(shí)現(xiàn)呢?
數(shù)據(jù)平臺(tái)如果支持將數(shù)據(jù)庫離線數(shù)據(jù)同步到Hive,Hive數(shù)據(jù)同步到Kafka,我們就可以編寫Hive SQL,建立ETL任務(wù)。把業(yè)務(wù)需要被刷新的數(shù)據(jù)同步到Kafka中,再消費(fèi)Kafka,把數(shù)據(jù)寫入到緩存中。在這個(gè)過程中通過數(shù)據(jù)平臺(tái)控制并發(fā)度,通過Kafka 分片和消費(fèi)線程并發(fā)度控制 緩存寫入的速率。
這個(gè)方案開發(fā)邏輯包括ETL 任務(wù),消費(fèi)Kafka寫入緩存。這兩部分的開發(fā)工作量不大。并且相比掃描全表任務(wù),ETL可以編寫更加復(fù)雜的SQL,修改后立即上線,無需自己控制并發(fā)、控制限流。在多個(gè)方面ETL刷緩存效率更高。
但是這個(gè)方案需要公司級(jí)別支持 多個(gè)存儲(chǔ)系統(tǒng)之間可以進(jìn)行數(shù)據(jù)同步。例如mysql、kafka、hive等。
除了首次上線,是否還有其他場景需要預(yù)熱緩存呢?
需要預(yù)熱緩存的其他場景
如果Redis掛了,數(shù)據(jù)怎么辦
剛才提到上線前,一定要進(jìn)行緩存預(yù)熱。還有一個(gè)場景:假設(shè)Redis掛了,怎么辦?全量的緩存數(shù)據(jù)都沒有了,全部請(qǐng)求同時(shí)打到數(shù)據(jù)庫,怎么辦。
除了首次上線需要預(yù)熱緩存,實(shí)際上如果緩存數(shù)據(jù)丟失后,也需要預(yù)熱緩存。所以預(yù)熱緩存的任務(wù)一定要開發(fā)的,一方面是上線前預(yù)熱緩存,同時(shí)也是為了保證緩存掛掉后,也能重新預(yù)熱緩存。
假如有大量數(shù)據(jù)冷啟動(dòng)怎么辦
假如促銷場景,例如春節(jié)搶紅包,平時(shí)非活躍用戶會(huì)在某個(gè)時(shí)間點(diǎn)大量打開App,這也會(huì)導(dǎo)致大量cache miss,進(jìn)而導(dǎo)致雪崩。此時(shí)就需要提前預(yù)熱緩存了。具體的辦法,可以考慮使用ETL任務(wù)。離線加載大量數(shù)據(jù)到Kafka,然后再同步到緩存。
總結(jié)
-
一定要預(yù)熱緩存,不然線上接口性能和數(shù)據(jù)庫真的扛不住。 -
可以通過灰度放量,掃描全表、ETL數(shù)據(jù)同步等方式預(yù)熱緩存 -
Redis掛了,大量用戶冷啟動(dòng)的促銷場景等場景都需要提前預(yù)熱緩存。
