1. 前言
在小型項目中(例如大部分 toB 業(yè)務),Redis 被作為緩存,我們無需過多關(guān)注緩存的性能,但是對于高并發(fā)的場景(例如 toC 的在線電商業(yè)務),在商品秒殺或者庫存搶購的時候,Redis 也可能存在諸多潛在的問題,例如緩存穿透、緩存雪崩。
2. 緩存問題
2.1 緩存穿透
面試官提問: Redis 的緩存穿透是什么意思?有什么解決方案?
題目解析:
?
首先給出緩存穿透的定義:用戶查詢一個本來在數(shù)據(jù)庫就沒有的數(shù)據(jù),導致每次請求要首先從緩存中查找,發(fā)現(xiàn)沒有之后再從持久化數(shù)據(jù)庫(例如 MySQL)中查找,最后返回空的過程。比如針對一個不存在的 user_id 查詢用戶信息,請求每次都會擊穿緩存打到數(shù)據(jù)庫上。
然后分析緩存穿透的危害:因為持久化數(shù)據(jù)庫的讀能力普遍低于緩存,緩存穿透越多,緩存命中率越低,這類請求可能被黑客利用從而打垮數(shù)據(jù)庫。
針對緩存穿透問題,業(yè)界有一些公認的解決方案:
(1)緩存空值:第一次查詢,在緩存和數(shù)據(jù)庫均查不到數(shù)據(jù),我們將 key=user_id
,value=null
這個鍵值對放入緩存,并且設(shè)置一個短期過期時間(例如 10 分鐘);第二次以及過期時間內(nèi)的查詢,流量會命中緩存,并且返回空結(jié)果。
這是最簡單粗暴的方法,如果對緩存的存儲數(shù)據(jù)有嚴格要求,一般不采用這種方案。
(2)預置布隆過濾器:布隆過濾器存儲緩存中所有的 key ,請求打進來之后,首先經(jīng)過布隆過濾器過濾,如果不存在,直接在該層攔截
請求,請求流量不會打到緩存以及數(shù)據(jù)庫。如果存在,則走正常的緩存、數(shù)據(jù)庫查詢邏輯。
2.2 緩存雪崩
面試官提問: Redis 的緩存雪崩是什么意思?有什么解決方案?
題目解析:
正如上文的分析,緩存的核心作用是為底層數(shù)據(jù)庫擋住大部分的外部流量,減輕數(shù)據(jù)庫的壓力。
如果緩存因為某種原因失效,例如 Redis Server 宕機或者在某個時間段大量的緩存 Key 過期,原本被緩存過濾的流量會直接打到數(shù)據(jù)庫上,給數(shù)據(jù)庫造成壓力,嚴重情況下可能導致數(shù)據(jù)庫宕機。
預防緩存雪崩也有多種方案:
(1)保證 Redis 的高可用,例如搭建 Redis Cluster,維護多集群。
(2)對服務請求進行限流,例如使用 Java 的 Hystrix 庫,Hystrix 能夠提供熔斷、限流、降低三種手段保證當極端情況發(fā)生時,打到數(shù)據(jù)庫的請求流量不會超過數(shù)據(jù)庫的承受能力。
- 熔斷:Hystrix 記錄某個接口的請求失敗率,當失敗率過高之后,拒絕后續(xù)請求,直接給出一個預設(shè)返回值;
- 限流:當請求 QPS 超過緩存的能力或者預先計算的上限后,將后續(xù)的的請求放入緩存隊列,防止請求高并發(fā)打進業(yè)務邏輯代碼;
- 降級:對于被拒絕訪問的請求,直接返回一個預設(shè)結(jié)果。降級最常見的應用例子是,電商秒殺的場景,當并發(fā)數(shù)超過業(yè)務服務能夠承受的閾值后,請求直接被網(wǎng)關(guān)層攔截,返回 "當前人數(shù)太多,請稍后重試" 的提示文案。
總結(jié)來說,預防緩存雪崩的本質(zhì)方案有:
- 加鎖:加鎖只是為了降低并發(fā)打到數(shù)據(jù)庫的流量,并沒有提高系統(tǒng)的吞吐量,當有 100 個用戶請求過來時,每次只能處理 1 個請求,用戶體驗差,生產(chǎn)環(huán)境基本不使用加鎖方案;
- 隊列:Hystrix 限流的本質(zhì)就請求放入緩存隊列,依次請求,生產(chǎn)環(huán)境必備方案;
- 拒絕服務:當請求超過隊列能夠處理的范疇后,直接攔截用戶請求,用戶體驗也差,一般是生產(chǎn)環(huán)境的兜底方案。
3. 小結(jié)
本章節(jié)介紹了使用 Redis 作為緩存容易遇到的兩大問題,緩存穿透和緩存雪崩。需要區(qū)分的是,緩存穿透是針對單個 Key 而言,緩存雪崩是多個 Key 失效,兩者產(chǎn)生的原因也不同。