緩存做爲DB前的一道防線,過濾了絕大部分DB請求,保障業務系統的高性能與高可用。redis
一樣的,特別是高度依賴緩存的分佈式系統,緩存設計若是不合理,輕則浪費機器資源,重則 1.緩存污染,即緩存與DB數據不一致,影響數據正常展現,如果在交易鏈路,則會形成資損;2.難以承載大流量,致使服務雪崩。數據庫
下面總結了分佈式系統中,緩存設計的常見問題與相應解法。緩存
緩存穿透,即請求了緩存中不存在的數據,致使請求打到DB,形成DB負載變高。若是出現大量緩存穿透請求,一是說明緩存命中率低,浪費機器資源;二是會存在將DB鏈接打滿、服務宕機的風險。bash
應對緩存擊穿有兩種經常使用方案:(1)對於無效數據,緩存中使用佔位符存儲;(2)使用布隆過濾器進行前置攔截。網絡
簡單來講,當數據無效 or 不存在時,使用特殊佔位符進行緩存。下一次請求來時,不須要走到DB,直接返回。併發
若是無效數據量比較大,使用佔位符的策略,會浪費大量內存資源。這種狀況可使用布隆過濾器。異步
布隆過濾器的原理:使用bitMap進行數據存儲,使用多個hash函數計算數據位。hash計算出入參的下標,並標記爲1。當計算出來的下標對應的bitMap都爲1時,則認爲該數據存在。反之,則不存在。分佈式
布隆過濾器的特性:函數
以下圖所示,入參爲商品productId,使用3個hash函數(H1,H2,H3)計算其hash值,做爲bitMap的下標。性能
productId1,標誌位爲[0,4,7],product2標誌位爲[2,4,10]
這時查詢請求進來,查productId3,假設計算出bitMap下標爲[2,4,9],對應占位爲[1,1,0],則productId3不存在。
使用布隆過濾器做爲緩存的前置過濾,能夠有效過濾掉無效數據請求。
兩種方案對比
佔位符:代碼實現簡單,數據時效性高,內存佔用較高,適用於無效數據量小的場景。
布隆過濾器:代碼實現複雜,數據時效性低,內存佔用低,適用於無效數據量大,且數據時效性要求不高的場景。
緩存擊穿,即某個熱點緩存失效後(緩存過時),該熱點key的請求直接打到DB,形成DB壓力瞬間增大。
解決方案:(1)熱點緩存永不過時;(2)加互斥鎖
通常是經過計算出邏輯上的過時時間,好比活動、商品過時時間endTime,再設置緩存過時時間爲endTime,保證在邏輯過時時間內,緩存一直有效。
固然若是內存滿了,緩存會啓動淘汰機制,視狀況會可能會淘汰永久緩存。相應解法以下:
方案一:作熱點緩存隔離,熱點緩存與普通緩存放在不一樣集羣,內存容量不受普通緩存數據影響;
方案二:緩存不作永不過時設置,而是設置過時時間,同時業務代碼加入緩存更新邏輯,在緩存過時前,更新緩存。實現方式能夠是mq、延時mq、異步線程等。
查DB操做前加鎖,同一時刻只能有一個請求打到DB,其餘請求等待重試。該方案引入分佈式鎖,將DB壓力轉移到分佈式鎖上,實現簡單,但存在請求過大時,等待時間過長,線程被打滿的風險。
示例代碼:
private finial static String QUERY_LOCK = "lock_"
public Object getDate(String key){
Object value = redis.get(key);
if (value == null) {
//設置分佈式鎖
if (redis.setnx(QUERY_LOCK + key, 1, time)) {
value = db.get(key);
redis.set(key, value, time);
redis.delete(QUERY_LOCK + key);
} else {
sleep(500);
return getDate(key);
}
}
return value;
}
複製代碼
緩存雪崩,即大量key在同一時間內同時過時,形成大量請求打到DB。
解法比較簡單,在設置緩存時,在基礎過時時間上,隨機浮動加減時間,避免緩存在同一時間失效。同時能夠在DAO層 or DB中間件 加入限流,避免瞬時大流量打垮DB。
緩存一致性,即緩存數據與DB數據的一致性。引入緩存後,同一份數據分佈在兩處,因爲網絡IO、數據庫事物、併發操做等因素,會形成緩存與DB數據不一致的問題。
而形成緩存數據不一致問題的主要緣由,基本都是由數據更新+併發操做致使。 簡單舉例:
算了,感受緩存一致性問題都能單開一章分析了。那麼下面的小結咱仍是留到後面再開新章節吧,感謝看到這裏的各位。