Redis緩存異常常用解決方案總結(jié)
前言
Redis緩存異常問題分別是:1.緩存雪崩。2.緩存預(yù)熱。3.緩存穿透。4.緩存降級。5.緩存擊穿,以及對應(yīng)Redis緩存異常問題解決方案。
1.緩存雪崩
1.1、什么是緩存雪崩
如果緩存集中在一段時(shí)間內(nèi)失效,發(fā)生大量的緩存穿透,所有的查詢都落在數(shù)據(jù)庫上,造成了緩存雪崩由于原有緩存失效,新緩存未到期間所有原本應(yīng)該訪問緩存的請求都去查詢數(shù)據(jù)庫了,而對數(shù)據(jù)庫CPU和內(nèi)存造成巨大壓力,嚴(yán)重的會造成數(shù)據(jù)庫宕機(jī)。
舉例來說, 我們在準(zhǔn)備一項(xiàng)搶購的促銷運(yùn)營活動,活動期間將帶來大量的商品信息、庫存等相關(guān)信息的查詢。
為了避免商品數(shù)據(jù)庫的壓力,將商品數(shù)據(jù)放入緩存中存儲。不巧的是,搶購活動期間,大量的熱門商品緩存同時(shí)失
效過期了,導(dǎo)致很大的查詢流量落到了數(shù)據(jù)庫之上,對于數(shù)據(jù)庫來說造成很大的壓力。
1.2、解決方案
1、加鎖排隊(duì)
mutex互斥鎖解決,Redis的SETNX去set一個(gè)mutex key,當(dāng)操作返回成功時(shí),再進(jìn)行加載數(shù)據(jù)庫的操作并回設(shè)緩存,否則,就重試整個(gè)get緩存的方法。
2、數(shù)據(jù)預(yù)熱
緩存預(yù)熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。這樣就可以避免在用戶請求的時(shí)候,先查詢數(shù)據(jù)庫,然后再將數(shù)據(jù)緩存的問題。用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)。可以通過緩存reload機(jī)制,預(yù)先去更新緩存,再即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存不同的key。
3、雙層緩存策略
C1為原始緩存,C2為拷貝緩存,C1失效時(shí),可以訪問C2,C1緩存失效時(shí)間設(shè)置為短期,C2設(shè)置為長期。
4、定時(shí)更新緩存策略
實(shí)效性要求不高的緩存,容器啟動初始化加載,采用定時(shí)任務(wù)更新或移除緩存。
5、設(shè)置不同的過期時(shí)間。
讓緩存失效的時(shí)間點(diǎn)盡量均勻。
2.緩存預(yù)熱
2.1、什么是緩存預(yù)熱
緩存預(yù)熱就是系統(tǒng)上線后,將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)。這樣就可以避免在用戶請求的時(shí)候,先查詢數(shù)據(jù)庫,然后再將數(shù)據(jù)緩存的問題。用戶直接查詢事先被預(yù)熱的緩存數(shù)據(jù)。
如圖所示:
如果不進(jìn)行預(yù)熱, 那么 Redis 初識狀態(tài)數(shù)據(jù)為空,系統(tǒng)上線初期,對于高并發(fā)的流量,都會訪問到數(shù)據(jù)庫中, 對數(shù)據(jù)庫造成流量的壓力。
2.2、解決方案
數(shù)據(jù)量不大的時(shí)候,工程啟動的時(shí)候進(jìn)行加載緩存動作;
數(shù)據(jù)量大的時(shí)候,設(shè)置一個(gè)定時(shí)任務(wù)腳本,進(jìn)行緩存的刷新;
數(shù)據(jù)量太大的時(shí)候,優(yōu)先保證熱點(diǎn)數(shù)據(jù)進(jìn)行提前加載到緩存。
3.緩存穿透
3.1、什么是緩存穿透
緩存穿透是指用戶查詢數(shù)據(jù),在數(shù)據(jù)庫沒有,自然在緩存中也不會有。這樣就導(dǎo)致用戶查詢的時(shí)候,在緩存中找不到對應(yīng)key的value,每次都要去數(shù)據(jù)庫再查詢一遍,然后返回空(相當(dāng)于進(jìn)行了兩次無用的查詢)。這樣請求就繞過緩存直接查數(shù)據(jù)庫。
3.2、解決方案
1、緩存空對象
簡單粗暴的方法,如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀?strong>把這個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會很短,最長不超過五分鐘。
2、布隆過濾器
優(yōu)勢:占用內(nèi)存空間很小,位存儲;性能特別高,使用key的hash判斷key存不存在。
將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會被這個(gè)bitmap攔截掉,從而避免了對底層存儲系統(tǒng)的查詢壓力。
4.緩存降級
降級的情況,就是緩存失效或者緩存服務(wù)掛掉的情況下,我們也不去訪問數(shù)據(jù)庫。
我們直接訪問內(nèi)存部分?jǐn)?shù)據(jù)緩存或者直接返回默認(rèn)數(shù)據(jù)。
舉例來說:
對于應(yīng)用的首頁,一般是訪問量非常大的地方,首頁里面往往包含了部分推薦商品的展示信息。這些推薦商品都會放到緩存中進(jìn)行存儲,同時(shí)我們?yōu)榱吮苊饩彺娴漠惓G闆r,對熱點(diǎn)商品數(shù)據(jù)也存儲到了內(nèi)存中。同時(shí)內(nèi)存中還保留了一些默認(rèn)的商品信息。
降級一般是有損的操作,所以盡量減少降級對于業(yè)務(wù)的影響程度。
5.緩存擊穿
5.1、什么是緩存擊穿
緩存擊穿是指緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù)(一般是緩存時(shí)間到期),這時(shí)由于并發(fā)用戶特別多,同時(shí)讀緩存沒
讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫去取數(shù)據(jù),引起數(shù)據(jù)庫壓力瞬間增大,造成過大壓力。
5.2、會帶來什么問題
會造成某一時(shí)刻數(shù)據(jù)庫請求量過大,壓力劇增。
5.3、解決方案
5.3.1.使用互斥鎖(mutex key)
這種解決方案思路比較簡單,就是只讓一個(gè)線程構(gòu)建緩存,其他線程等待構(gòu)建緩存的線程執(zhí)行完,重新從緩存獲取數(shù)據(jù)就可以了。
如果是單機(jī),可以用synchronized或者lock來處理;
如果是分布式環(huán)境可以用分布式鎖就可以了(分布式鎖,可以用memcache的add, redis的setnx, zookeeper的添加節(jié)點(diǎn)操作)。
5.3.2.永遠(yuǎn)不過期
從redis上看,確實(shí)沒有設(shè)置過期時(shí)間,這就保證了,不會出現(xiàn)熱點(diǎn)key過期問題,也就是“物理”不過期。
從功能上看,如果不過期,那不就成靜態(tài)的了嗎?所以我們把過期時(shí)間存在key對應(yīng)的value里,如果發(fā)現(xiàn)要過期了,通過一個(gè)后臺的異步線程進(jìn)行緩存的構(gòu)建,也就是“邏輯”過期。
5.3.3.緩存屏障
該方法類似于方法一:
使用countDownLatch和atomicInteger.compareAndSet()方法,實(shí)現(xiàn)輕量級鎖。
public class MyCache{ ? private ConcurrentHashMap<String, String> map; ? private CountDownLatch countDownLatch; ? private AtomicInteger atomicInteger; ? public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch, AtomicInteger atomicInteger) { this.map = map; this.countDownLatch = countDownLatch; this.atomicInteger = atomicInteger; } ? public String get(String key){ ? String value = map.get(key); if (value != null){ System.out.println(Thread.currentThread().getName()+"\t 線程獲取value值 value="+value); return value; } // 如果沒獲取到值 // 首先嘗試獲取token,然后去查詢db,初始化化緩存; // 如果沒有獲取到token,超時(shí)等待 if (atomicInteger.compareAndSet(0,1)){ System.out.println(Thread.currentThread().getName()+"\t 線程獲取token"); return null; } ? // 其他線程超時(shí)等待 try { System.out.println(Thread.currentThread().getName()+"\t 線程沒有獲取token,等待中。。。"); countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 初始化緩存成功,等待線程被喚醒 // 等待線程等待超時(shí),自動喚醒 System.out.println(Thread.currentThread().getName()+"\t 線程被喚醒,獲取value ="+map.get("key")); return map.get(key); } ? public void put(String key, String value){ ? try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } ? map.put(key, value); ? // 更新狀態(tài) atomicInteger.compareAndSet(1, 2); ? // 通知其他線程 countDownLatch.countDown(); System.out.println(); System.out.println(Thread.currentThread().getName()+"\t 線程初始化緩存成功!value ="+map.get("key")); } ? } ? public class MyThread implements Runnable{ ? private MyCache myCache; ? public MyThread(MyCache myCache) { this.myCache = myCache; } ? @Override public void run() { String value = myCache.get("key"); if (value == null){ myCache.put("key","value"); } ? } } ? public class CountDownLatchDemo { public static void main(String[] args) { ? MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0)); ? MyThread myThread = new MyThread(myCache); ? ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { executorService.execute(myThread); } } }
到此這篇關(guān)于Redis緩存異常常用解決方案總結(jié)的文章就介紹到這了,更多相關(guān)Redis緩存異常內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis+lua實(shí)現(xiàn)限流的項(xiàng)目實(shí)踐
redis有很多限流的算法(比如:令牌桶,計(jì)數(shù)器,時(shí)間窗口)等,在分布式里面進(jìn)行限流的話,我們則可以使用redis+lua腳本進(jìn)行限流,下面就來介紹一下redis+lua實(shí)現(xiàn)限流2023-10-10Redis整合SpringBoot的RedisTemplate實(shí)現(xiàn)類(實(shí)例詳解)
這篇文章主要介紹了Redis整合SpringBoot的RedisTemplate實(shí)現(xiàn)類,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01redis列表類型_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了redis列表類型的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08Redis 實(shí)現(xiàn)分布式鎖時(shí)需要考慮的問題解決方案
本文詳細(xì)探討了使用Redis實(shí)現(xiàn)分布式鎖時(shí)需要考慮的問題,包括鎖的競爭、鎖的釋放、超時(shí)管理、網(wǎng)絡(luò)分區(qū)等,并提供了相應(yīng)的解決方案和代碼實(shí)例,有助于開發(fā)者正確且安全地使用Redis實(shí)現(xiàn)分布式鎖2024-09-09Redis筆記點(diǎn)贊排行榜的實(shí)現(xiàn)示例
探店筆記類似點(diǎn)評網(wǎng)站的評價(jià),本文主要介紹了Redis筆記點(diǎn)贊排行榜的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Redis大key多key拆分實(shí)現(xiàn)方法解析
這篇文章主要介紹了Redis大key多key拆分實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11