1. 程式人生 > 實用技巧 >Redis快取相關問題(穿透、雪崩、擊穿)及解決辦法

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; //有資料直接返回     
	      }
	   }