1. 程式人生 > >【redis學習之七】基於Redis的分散式快取常見問題

【redis學習之七】基於Redis的分散式快取常見問題

在使用redis做全域性快取的時候,基本的流程大概是這樣的:


    大概流程即為:

①判斷快取是否存在

②若存在則直接返回呼叫端

③若不存在則從資料庫載入資料

④將載入的資料寫入快取

⑤返回呼叫端

    但是現實使用過程中還是有一些問題值得討論一番

1、redis和資料庫雙寫一致性問題:

      當發生寫資料庫操作的時候,若是insert情況,那我們在插入資料庫後,是否也需將新寫入的資料更新到快取裡呢?若是update情況,我們是先刪除快取再更新資料庫呢?還是先更新資料庫再刪除舊快取呢?更新完資料庫是否要將新value寫入快取呢?這裡有一些最佳實踐:

①insert操作情況下,若是熱點資料則需寫入快取,若是非熱點,可以等到有讀請求時再獲取資料寫入快取

②update情況,優先刪除快取再更新資料庫,再根據具體業務場景判斷是否寫入快取,若是更新較多的場景,那就無需寫入,若是讀較多則更新

③上述update情況,若是讀操作大大多於寫操作,可能存在此種狀況:在資料庫讀寫分離架構下,A執行緒更新資料時先刪除快取,然後更新了主庫資料庫值,此時B執行緒查詢此快取不存在,去從庫資料庫查到了舊資料(此時主從複製尚未完成)並將舊資料寫入快取,此時快取中將一直存在的是舊資料。此時可以採取的策略是快取雙刪策略,即是A執行緒先刪除快取再更新資料庫,然後執行緒休眠一定時間(例如1s)後再將key刪除,確保最新的資料會被讀取並寫入快取。若是不想A執行緒阻塞休眠影響響應速度,可以起一個非同步執行緒去完成快取刪除操作

2、快取擊穿問題

    快取穿透即是指有在快取層查詢無記錄,然後請求擊穿快取層,將訪問資料庫獲取資料,在高併發的場景下,使用頻率較高的key失效後可能一瞬間會有大量的請求穿透快取進行資料庫訪問,造成資料庫層壓力過大。還有一種快取穿透問題,黑客故意虛擬了很多快取中不存在的key作為請求,而這些請求由於快取中無記錄將全部擊穿到資料庫,參考解決方案如下:

    1、針對惡意攻擊可提供一個能迅速判斷請求有效性的攔截機制,例如布隆過濾器,內部維護了儘可能全的可能存在的key,而請求到來時,判斷若key在過濾器中存在才透傳到快取層,否則直接拒絕或返回空值

    2、針對快取中沒有,資料庫中也沒有記錄的情況,我們可以仍然將key快取起來,但是設定一個較短的過期時間,這樣如果下次仍然請求這個不存在的key也將取到快取不再訪問資料庫,但是此種做法就是比較佔用記憶體

    3、針對快取失效後大量請求擊穿到資料庫的問題,可以使用分散式鎖來控制只有獲取到鎖的執行緒才可以去訪問資料庫,其餘執行緒可等待一會兒再次查詢快取,樣例如下,這樣能保證只有一個請求能落到資料庫,其餘仍從快取中獲取:

public Object getValue(String key){
    	Jedis jedis = getJedis();
    	//若key存在則直接獲取快取後返回
    	if(jedis.exists(key)){
    		return jedis.get(key);
    	}else{
    		//若獲取分散式鎖成功則查詢DB獲取資料
    		if(jedis.setnx(key+"Mutex", "")==1){
    			Object value = getValueFromDB();
    			// 回寫快取
    			jedis.set(key, value.toString());
    			// 刪除鎖
    			jedis.del(key+"Mutex");
    			return value;
    		}else{
    			// 若未獲取到鎖則睡眠50毫秒再試
    			Thread.currentThread().sleep(50);
    			getValue(key);
    		}
    	}
    	
    }

3、快取雪崩問題:

    快取雪崩即是大量的key在同一時間失效,高併發場景下造成大量請求直接訪問資料庫導致資料庫壓力過大影響響應速度甚至服務宕機,解決方案有如下思路:

    1、在設定快取失效時間時可在末位設定一個隨機值,避免集體失效

    2、使用互斥鎖,此方案會導致吞吐量大大降低

    3、雙快取策略,使用A,B兩個快取層,B快取設定過期時間略晚於A,若A未取到快取則繼續訪問B,得到value後再非同步起一個執行緒去更新A、B快取,此種情況需要B做快取預熱,即在應用啟動的時候直接將快取寫入到快取系統,無需請求來了之後再去寫入快取