1. Redis緩存雪崩
發(fā)生場景
當Redis服務(wù)器重啟或者大量緩存在同一時期失效時,此時大量的流量會全部沖擊到數(shù)據(jù)庫上面,數(shù)據(jù)庫有可能會因為承受不住而宕機霹肝;
所以此時的緩存層出現(xiàn)了錯誤,于是所有的請求都會到達存儲層错负,存儲層的調(diào)用量會暴漲氓仲,造成存儲層也完蛋了的情況;
解決方案
- 均勻分布 : 我們應(yīng)該在設(shè)置失效時間時應(yīng)該盡量均勻的分布,比如失效時間是當前時間加上一個時間段的隨機值鳍烁,讓失效的時間保持隨機性叨襟,不在同一時間點失效。
- 熔斷機制 : 類似于SpringCloud的熔斷器,我們可以設(shè)定閾值或監(jiān)控服務(wù),如果達到熔斷閾值(QPS,服務(wù)無法響應(yīng),服務(wù)超時)時,則直接返回,不再調(diào)用目標服務(wù),并且還需要一個檢測機制,如果目標服務(wù)已經(jīng)可以正常使用,則重置閾值,恢復使用幔荒。
- 隔離機制 : 類似于Docker一樣,當一個服務(wù)器上某一個tomcat出了問題后不會影響到其它的tomcat,這里我們可以使用線程池來達到隔離的目的,當線程池執(zhí)行拒絕策略后則直接返回,不再向線程池中增加任務(wù)糊闽。
- 限流/降流機制 : 其實限流就是熔斷機制的一個版本,設(shè)置閾值(QPS),達到閾值之后直接返回。
- 雙緩存機制 : 將數(shù)據(jù)存儲到緩存中時存儲倆份,一份的有效期是正常的,一份的有效期長一點.不建議用這個方案,因為比較消耗內(nèi)存資源,畢竟Redis是直接存儲到內(nèi)存中的爹梁。
- 集群/redis高可用:既然一臺redis扛不住右犹,那就多弄幾臺,這臺掛掉還有其它的可以繼續(xù)工作姚垃,也就是搭建的一個集群念链。
2. Redis緩存穿透
發(fā)生場景
當用戶想查詢某條數(shù)據(jù),發(fā)現(xiàn)redis數(shù)據(jù)庫沒有积糯,即緩存沒有命中掂墓;繼續(xù)向持久層數(shù)據(jù)庫查詢,還是沒有看成,即本次查詢失斁唷;當很多次用戶查詢都失敗川慌,于是請求都去請求 了持久層吃嘿;此時持久層的數(shù)據(jù)庫就會產(chǎn)生很大很大壓力,于是就出現(xiàn)了緩存穿透梦重。
解決方案
布隆過濾 : 我們可以預先將數(shù)據(jù)庫里面所有的key全部存到一個大的map里面,然后在過濾器中過濾掉那些不存在的key.但是需要考慮數(shù)據(jù)庫的key是會更新的,此時需要考慮數(shù)據(jù)庫 --> map的更新頻率問題
緩存空值 : 哪怕這條數(shù)據(jù)不存在但是我們?nèi)稳粚⑵浯鎯Φ骄彺嬷腥?設(shè)置一個較短的過期時間即可,并且可以做日志記錄,尋找問題原因
3. Redis緩存擊穿
緩存擊穿兑燥,是指一個key非常熱點,在不停的扛著大并發(fā)琴拧,大并發(fā)集中對這一個點進行訪問降瞳,當這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存艾蓝,直接請求數(shù)據(jù)庫力崇,就像在一個屏障上鑿開了一個洞。
比如當前的商品降價赢织,促銷亮靴;或者微博熱搜很多人同時訪問,那么這個Key就是一個熱點于置,同時扛著超高并發(fā)茧吊,集中訪問贞岭,緩存就被擊穿。
解決方案
1.使用互斥鎖(mutex key)
業(yè)界比較常用的做法搓侄,是使用mutex瞄桨。簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空)讶踪,不是立即去load db芯侥,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時乳讥,再進行l(wèi)oad db的操作并回設(shè)緩存柱查;否則,就重試整個get緩存的方法云石。
SETNX唉工,是「SET if Not eXists」的縮寫,也就是只有不存在的時候才設(shè)置汹忠,可以利用它來實現(xiàn)鎖的效果淋硝。在redis2.6.1之前版本未實現(xiàn)setnx的過期時間,所以這里給出兩種版本代碼參考:
//2.6.1前單機版本鎖
String get(String key) {
String value = redis.get(key);
if (value == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 3 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其他線程休息50毫秒后重試
Thread.sleep(50);
get(key);
}
}
}
最新版本代碼:
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表緩存值過期
//設(shè)置3min的超時宽菜,防止del操作失敗的時候谣膳,下次緩存過期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表設(shè)置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //這個時候代表同時候的其他線程已經(jīng)load db并回設(shè)到緩存了,這時候重試獲取緩存值即可
sleep(50);
get(key); //重試
}
} else {
return value;
}
}
mem
文獻參考:
https://baijiahao.baidu.com/sid=1655304940308056733&wfr=spider&for=pc
https://blog.csdn.net/zeb_perfect/article/details/54135506