Redis快取相關問題(穿透、雪崩、擊穿)及解決辦法
目錄
首先,我們需要了解使用Redis快取查詢資料的流程是:
1.資料查詢首先進行快取查詢。
2.如果資料存在則直接返回快取資料。
3.如果資料不存在,就對資料庫進行查詢,並把查詢到的資料放進快取。
4.如果資料庫查詢資料為空,則不放進快取。
//虛擬碼: ServiceImpl.java Public String getId(String cacheKey){ String id = redisDao.get(cacheKey);//先查詢redis快取 If(id == null){ //快取資料不存在,查詢資料庫 String iddb = idDao.find();//查詢資料庫 If(iddb != null ){//判斷資料庫資料是否存在 redisDao.set(cacheKey,iddb);//存在,儲存到redis中 return iddb; } }else{ Retrun id; //快取資料存在,直接返回資料 } }
快取穿透
概念:
快取穿透是指一直查詢快取和資料庫中都不存在的資料。比如id為-1的資料
現象:
解決方案:
1.資料校驗
1.1.先從redis查詢資料,redis有,查詢出來返回
1.2.如果redis沒有查詢出資料,查詢資料庫,查詢出來返回,並快取到redis中
1.3.如果資料庫沒有查詢出資料,設定預設資料,儲存到redis中,並設定有效期
1.4.再次查詢的時候,判斷redis中獲取的是否為預設值,是直接返回,不是則操作再返回。
//虛擬碼 public object GetProductListNew() { int cacheTime = 30;//快取時間 String cacheKey = "product_list";//快取的key String cacheValue = CacheHelper.Get(cacheKey);//獲取redis資料 if(cacheValue != string.Empty){//判斷redis中儲存的是否為預設值 if (cacheValue != null) {//查詢資料不為null return cacheValue;//返回查詢出來的資料 }else {//redis查詢不出來資料 //資料庫查詢資料 cacheValue = GetProductListFromDB(); if (cacheValue == null) {//資料查詢資料庫為null //如果發現為空,設定個預設值,也快取起來 cacheValue = string.Empty; } CacheHelper.Add(cacheKey, cacheValue, cacheTime);//把資料儲存到redis return cacheValue; } }else{ return cacheValue;//返回預設值,方便呼叫者判斷 } }
2.布隆過濾器
https://www.jianshu.com/p/400dd82389b4?from=groupmessage
快取雪崩
概念:
快取雪崩是指在某一個時間段,redis快取的資料集中全部過期失效,在快取集中失效的這個時
間段對資料的訪問查詢,都落到了資料庫上,對於資料庫而言,就會產生週期性的壓力。
現象:
解決方案:
1.在批量往Redis存資料的時候,把每個資料的失效時間儘量都不一致,加鎖排隊或者加個隨機值。
1.1.加鎖排隊
//虛擬碼 public object GetProductListNew() { int cacheTime = 30; String cacheKey = "product_list"; String lockKey = cacheKey; String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { synchronized(lockKey) { cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //這裡一般是sql查詢資料 cacheValue = GetProductListFromDB(); CacheHelper.Add(cacheKey, cacheValue, cacheTime); } } return cacheValue; } }
1.2.隨機值:
//虛擬碼
public object GetProductListNew() {
int cacheTime = Random.nextInt(30);
String cacheKey = "product_list";
//快取標記
String cacheSign = cacheKey + "_sign";
String sign = CacheHelper.Get(cacheSign);
//獲取快取值
String cacheValue = CacheHelper.Get(cacheKey);
if (sign != null) {
return cacheValue; //未過期,直接返回
} else {
CacheHelper.Add(cacheSign, "1", cacheTime);
ThreadPool.QueueUserWorkItem((arg) -> {
//這裡一般是 sql查詢資料
cacheValue = GetProductListFromDB();
//日期設快取時間的2倍,用於髒讀
CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);
});
return cacheValue;
}
}
2.熱門的資料(訪問頻率高的資料)可以快取的時間長一些。
3.冷門的資料可以快取的時間短一些。
4.特別熱門的資料可以設定永不過期。
快取擊穿
概念:
指一個Key非常熱點,在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個Key在失效的瞬間,持續的大併發就穿破快取,直接請求資料庫,就像在一個完好無損的桶上鑿開了一個洞。
現象
解決方案:
1.設定熱點資料永遠不過期。
2.設定互斥鎖
簡單地來說,就是在快取失效的時候(判斷拿出來的值為空),不是立即去load db,
而是先使用快取工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設快取;否則,就重試整個get快取的方法。
//虛擬碼
public String get(key) {
String value = redis.get(key);//獲取redis的資料
if (value == null) { //代表快取值過期
//設定3min的超時,防止del操作失敗的時候,
//下次快取過期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表設定成功
value = db.get(key);//資料庫查詢資料
redis.set(key, value, expire_secs);//redis設定資料
redis.del(key_mutex);//刪除key_mutes
} else {
//這個時候代表同時候的其他執行緒已經load db並回設到快取了,
//這時候重試獲取快取值即可
sleep(50);
get(key); //重試
}
} else {
return value; //有資料直接返回
}
}