作者:熬夜不加班
連結:https://www.jianshu.com/p/9da8133be99f
Redis網紅面試題三連
面試題1:怎麽解決緩存穿透問題的?
面試題2:說一下緩存擊穿吧,你們是怎麽解決的?
面試題3:那緩存雪崩說說你們是怎麽解決的?
面試題1:怎麽解決緩存穿透問題的?
緩存穿透:指緩存和資料庫中都沒有的數據,導致所有的請求都打到資料庫上,然後資料庫還查不到(如null),沒法寫緩存,造成資料庫短時間執行緒數被打滿而導致其他服務阻塞,最終導致線上服務不可用。此時緩存就好像被穿透了一樣,起不到任何作用。
當然,使用緩存難免會有穿透的發生。
緩存容量有限,不可能去緩存所有數據,查詢到未被緩存的數據就會發生穿透是正常情況。
互聯網業務的數據存取模型一般是遵循二八原則的,即 20% 的數據為熱點數據,80% 的數據是非熱點不被常存取的數據。既然緩存容量有限,且20%的數據為熱點數據,那我們可以利用有限的容量去緩存那 20% 的數據來保護我們的系統,至於80%非熱點不常用的數據發生穿透就穿透了,資料庫吃得住。
那我們怎樣來解決這種緩存穿透問題呢?
介面參數校驗:
防君子不防小人。在參數校驗層加上參數合法性校驗,如查詢訂單ID為20位隨機值,正則核對一下ID長度是否規範,不規範地直接過濾掉。
設定空值:
當存取緩存和DB都沒有查詢到值時,該key我們當做是惡意參數來看,可以將該key的空值寫進緩存,設定較短的過期時間。
但是如果有大量的獲取並不存在數據的穿透請求的話如惡意攻擊,則會浪費緩存空間,如果這種null值過量的話,還會淘汰掉本身緩存存在的數據,這就會使我們的緩存命中率下降。
因此在使用設定空值方案時,我們要做好監控,預防緩存空間被過多null值占領造成的緩存空間浪費,如果這種數據量太大,就不再建議使用,那就使用另一種方案,即布隆過濾器。
布隆過濾器:
布隆過濾器在查詢緩存之前起到初步過濾作用,布隆過濾器儲存所有可能存取的 key,將不存在的 key 直接過濾,存在的 key 再進一步查詢緩存和資料庫。
布隆過濾器的特點是判斷不存在的,則一定不存在;判斷存在的,大機率存在,但也有小機率不存在。並且這個機率是可控的,根據具體需求,我們可以讓這個機率小幅降低或變高。
布隆過濾器由一個 bitSet 和 一組 Hash 函式(演算法)組成,是一種空間效率極高的機率型演算法和數據結構,透過二進制來進行數據儲存。在初始化時,bitSet 的每一位被初始化為0。
當數據加入布隆過濾器集合時,流程如下:
經過K個哈希函式計算該數據,返回K個計算出的hash值
這些K個hash值對映到對應的K個二進制的陣列下標
將K個下標對應的二進制數據改為1。
布隆過濾器查詢一個key是否在集合中,流程如下:
經過K個哈希函式計算該數據,對應計算出的K個hash值
經過hash值找到對應的二進制的陣列下標
如果存在其中一處位置的二進制數據是0,那麽該數據不存在。若是都是1,該數據存在集合中(但由於存在Hash碰撞,判斷數據存在時可能存在誤判)。
布隆過濾器的優缺點
優勢
因為儲存的是二進制數據,因此占用的空間很小;
它的插入和查詢速度是很是快的,時間復雜度是O(K),能夠聯想一下HashMap的過程;
保密性很好,由於自己不儲存任何原始數據,只有二進制數據
缺點
存在誤判
添加數據是經過計算數據的hash值,hash是存在碰撞的,也就是說,存在兩個不一樣的數據計算獲得相同的hash值。
例如圖中的你好和hello,假如最終算出hash值相同,那麽他們會將同一個下標的二進制數據改成1。因此也無法確定key為你好和hello是否存在。
刪除困難
如上,你好和hello的hash值相同,對應的陣列下標也是同樣的。如果想刪除你好,即將座標值改為0,可能會影響到其他key,比如是否會連hello都一塊兒刪了之類的。
面試題2:說一下緩存擊穿吧,你們是怎麽解決的?
緩存擊穿:指緩存中沒有但資料庫中有的數據(一般是熱點數據緩存時間到期),這時由於並行使用者特別多,同時讀緩存沒讀到數據,又同時去資料庫去查,引起資料庫壓力瞬間增大,線上系統卡住。
解決方案:
1、加互斥鎖(mutex key)。 在並行的多個請求中,只有第一個請求執行緒能拿到鎖並執行資料庫查詢操作,其他的執行緒拿不到鎖就阻塞等著,等到第一個執行緒將數據寫入緩存後,直接走緩存。
互斥鎖
緩存擊穿後,多個執行緒會同時去查詢資料庫的這條數據,那麽我們可以在第一個查詢數據的請求上使用一個互斥鎖來鎖住它。
其他的執行緒走到這一步拿不到鎖就等著,等第一個執行緒查詢到了數據,然後做緩存。後面的執行緒進來發現已經有緩存了,就直接走緩存。
static Lock reenLock = new ReentrantLock();
public List<String> getData04() throws InterruptedException {
List<String> result = new ArrayList<String>();
// 從緩存讀取數據
result = getDataFromCache();
if (result.isEmpty()) {
if (reenLock.tryLock()) {
try {
System.out.println("拿到鎖了,從DB獲取資料庫後寫入緩存");
// 從資料庫查詢數據
result = getDataFromDB();
// 將查詢到的數據寫入緩存
setDataToCache(result);
} finally {
reenLock.unlock();// 釋放鎖
}
} else {
result = getDataFromCache();// 先查一下緩存
if (result.isEmpty()) {
System.out.println("我沒拿到鎖,緩存也沒數據,先小憩一下");
Thread.sleep(100);// 小憩一會兒
return getData04();// 重試
}
}
}
return result;
}
2、熱點數據不過期。 根據實際業務情況,在Redis中維護一個熱點數據表,批次設為永不過期(如top1000),並定時更新top1000數據。
這種方式適用於比較極端的場景,例如流量特別特別大的場景,使用時需要考慮業務能接受數據不一致的時間,還有就是異常情況的處理,不要到時候緩存重新整理不上,一直是臟數據,那就涼了。
面試題3:那緩存雪崩說說你們是怎麽解決的?
緩存雪崩:大量的熱點 key 設定了相同的過期時間,導致緩存在同一時刻全部失效,造成瞬時資料庫請求量大、壓力驟增,引起雪崩,甚至導致資料庫被打掛。緩存雪崩其實有點像升級版的緩存擊穿,緩存擊穿是一個熱點 key,緩存雪崩是一組熱點 key。
解決方案:
1、過期時間打散: 既然是大量緩存集中失效,那最容易想到就是讓他們不集中生效。可以給緩存的過期時間加上一個隨機值時間,使得每個 key 的過期時間分布開來,不會集中在同一時刻失效。
2、熱點數據不過期: 緩存永不過期,異步更新緩存數據。雖然不會出現雪崩效應,卻無法保證數據的一致性。
3、加互斥鎖: jvm鎖機制、分布式鎖機制都可以。該方式和緩存擊穿一樣,按 key 維度加鎖,對於同一個 key,只允許一個執行緒去計算,其他執行緒原地阻塞等待第一個執行緒的計算結果,然後直接走緩存即可。