黑馬點評筆記 redis緩存三大問題解決

文章目錄

  • 緩存問題
    • 緩存穿透問題的解決思路
      • 編碼解決商品查詢的緩存穿透問題
    • 緩存雪崩問題及解決思路
    • 緩存擊穿問題及解決思路
      • 問題分析
      • 使用鎖來解決
        • 代碼實現
      • 邏輯過期方案
        • 代碼實現

緩存問題

我們熟知的是用到緩存就會遇到緩存三大問題:

  • 緩存穿透
  • 緩存擊穿
  • 緩存雪崩

接下來讓我介紹在黑馬點評中這三個問題是如何解決了。

緩存穿透問題的解決思路

緩存穿透 :緩存穿透是指客戶端請求的數據在緩存中和數據庫中都不存在,這樣緩存永遠不會生效,這些請求都會打到數據庫。

常見的解決方案有兩種:

  • 緩存空對象
    • 優點:實現簡單,維護方便
    • 缺點:
      • 額外的內存消耗
      • 可能造成短期的不一致
  • 布隆過濾
    • 優點:內存占用較少,沒有多余key
    • 缺點:
      • 實現復雜
      • 存在誤判可能

緩存空對象思路分析:當我們客戶端訪問不存在的數據時,先請求redis,但是此時redis中沒有數據,此時會訪問到數據庫,但是數據庫中也沒有數據,這個數據穿透了緩存,直擊數據庫,我們都知道數據庫能夠承載的并發不如redis這么高,如果大量的請求同時過來訪問這種不存在的數據,這些請求就都會訪問到數據庫,簡單的解決方案就是哪怕這個數據在數據庫中也不存在,我們也把這個數據存入到redis中去,這樣,下次用戶過來訪問這個不存在的數據,那么在redis中也能找到這個數據就不會進入到緩存了

布隆過濾:布隆過濾器其實采用的是哈希思想來解決這個問題,通過一個龐大的二進制數組,走哈希思想去判斷當前這個要查詢的這個數據是否存在,如果布隆過濾器判斷存在,則放行,這個請求會去訪問redis,哪怕此時redis中的數據過期了,但是數據庫中一定存在這個數據,在數據庫中查詢出來這個數據后,再將其放入到redis中,假設布隆過濾器判斷這個數據不存在,則直接返回。

這種方式優點在于節約內存空間,存在誤判,誤判原因在于:布隆過濾器走的是哈希思想,只要哈希思想,就可能存在哈希沖突
在這里插入圖片描述

編碼解決商品查詢的緩存穿透問題

查詢數據時,如果這個數據不存在,還是會把這個數據寫入到Redis中,并且將value設置為空,歐當再次發起查詢時,我們如果發現命中之后,判斷這個value是否是null,如果是null,則是之前寫入的數據,證明是緩存穿透數據,如果不是,則直接返回數據。
在這里插入圖片描述

public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String json = stringRedisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isNotBlank(json)) {// 3.存在,直接返回return JSONUtil.toBean(json, type);}// 判斷命中的是否是空值if (json != null) {// 返回一個錯誤信息return null;}// 4.不存在,根據id查詢數據庫R r = dbFallback.apply(id);// 5.不存在,返回錯誤if (r == null) {// 將空值寫入redisstringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回錯誤信息return null;}// 6.存在,寫入redisthis.set(key, r, time, unit);return r;}

緩存雪崩問題及解決思路

緩存雪崩是指在同一時段大量的緩存key同時失效或者Redis服務宕機,導致大量請求到達數據庫,帶來巨大壓力。

解決方案

  • 給不同的Key的TTL添加隨機值
  • 利用Redis集群提高服務的可用性
  • 給緩存業務添加降級限流策略
  • 給業務添加多級緩存
    在這里插入圖片描述
    一般情況下,我們都采用隨機值過期的來解決雪崩問題。

緩存擊穿問題及解決思路

緩存擊穿問題也叫熱點Key問題,就是一個被高并發訪問并且緩存重建業務較復雜的key突然失效了,無數的請求訪問會在瞬間給數據庫帶來巨大的沖擊。

常見的解決方案有兩種:

互斥鎖方案:由于保證了互斥性,所以數據一致,且實現簡單,因為僅僅只需要加一把鎖而已,也沒其他的事情需要操心,所以沒有額外的內存消耗,缺點在于有鎖就有死鎖問題的發生,且只能串行執行性能肯定受到影響

邏輯過期方案 :線程讀取過程中不需要等待,性能好,有一個額外的線程持有鎖去進行重構數據,但是在重構數據完成前,其他的線程只能返回之前的數據,且實現起來麻煩

問題分析

假設線程1在查詢緩存之后,本來應該去查詢數據庫,然后把這個數據重新加載到緩存的,此時只要線程1走完這個邏輯,其他線程就都能從緩存中加載這些數據了,但是假設在線程1沒有走完的時候,后續的線程2,線程3,線程4同時過來訪問當前這個方法, 那么這些線程都不能從緩存中查詢到數據,那么他們就會同一時刻來訪問查詢緩存,都沒查到,接著同一時間去訪問數據庫,同時的去執行數據庫代碼,對數據庫訪問壓力過大,如果有人利用此漏洞進行攻擊就會導致數據庫崩潰。
在這里插入圖片描述

使用鎖來解決

因為鎖能實現互斥性。假設線程過來,只能一個人一個人的來訪問數據庫,從而避免對于數據庫訪問壓力過大,但這也會影響查詢的性能,因為此時會讓查詢的性能從并行變成了串行,我們可以采用tryLock方法 + double check來解決這樣的問題。

假設現在線程1過來訪問,他查詢緩存沒有命中,但是此時他獲得到了鎖的資源,那么線程1就會一個人去執行邏輯,假設現在線程2過來,線程2在執行過程中,并沒有獲得到鎖,那么線程2就可以進行到休眠,直到線程1把鎖釋放后,線程2獲得到鎖,然后再來執行邏輯,此時就能夠從緩存中拿到數據了。

在這里插入圖片描述

代碼實現

加鎖的核心方案就是在redis未查詢到數據時,如果向數據庫就要獲取鎖,未獲取到鎖的線程就暫停執行,這樣就可以把原來并行的程序變成單線程的程序,從而減少并發帶來的問題。

public Shop queryWithMutex(Long id)  {String key = CACHE_SHOP_KEY + id;// 1、從redis中查詢商鋪緩存String shopJson = stringRedisTemplate.opsForValue().get("key");// 2、判斷是否存在if (StrUtil.isNotBlank(shopJson)) {// 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判斷命中的值是否是空值if (shopJson != null) {//返回一個錯誤信息return null;}// 4.實現緩存重構//4.1 獲取互斥鎖String lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判斷否獲取成功if(!isLock){//4.3 失敗,則休眠重試Thread.sleep(50);return queryWithMutex(id);}//4.4 成功,根據id查詢數據庫shop = getById(id);// 5.不存在,返回錯誤if(shop == null){//將空值寫入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);//返回錯誤信息return null;}//6.寫入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);}catch (Exception e){throw new RuntimeException(e);}finally {//7.釋放互斥鎖unlock(lockKey);}return shop;}

邏輯過期方案

方案分析:我們之所以會出現這個緩存擊穿問題,主要原因是在于我們對key設置了過期時間,假設我們不設置過期時間,其實就不會有緩存擊穿的問題,但是不設置過期時間,這樣數據不就一直占用我們內存了嗎,我們可以采用邏輯過期方案。

我們把過期時間設置在 redis的value中,注意:這個過期時間并不會直接作用于redis,而是我們后續通過邏輯去處理。假設線程1去查詢緩存,然后從value中判斷出來當前的數據已經過期了,此時線程1去獲得互斥鎖,那么其他線程會進行阻塞,獲得了鎖的線程他會開啟一個 線程去進行 以前的重構數據的邏輯,直到新開的線程完成這個邏輯后,才釋放鎖, 而線程1直接進行返回,假設現在線程3過來訪問,由于線程線程2持有著鎖,所以線程3無法獲得鎖,線程3也直接返回數據,只有等到新開的線程2把重建數據構建完后,其他線程才能走返回正確的數據。

這種方案巧妙在于,異步的構建緩存,缺點在于在構建完緩存之前,返回的都是臟數據。

代碼實現

核心思路:相較于原來從緩存中查詢不到數據后直接查詢數據庫而言,現在的方案是 進行查詢之后,如果從緩存沒有查詢到數據,則進行互斥鎖的獲取,獲取互斥鎖后,判斷是否獲得到了鎖,如果沒有獲得到,則休眠,過一會再進行嘗試,直到獲取到鎖為止,才能進行查詢

如果獲取到了鎖的線程,再去進行查詢,查詢后將數據寫入redis,再釋放鎖,返回數據,利用互斥鎖就能保證只有一個線程去執行操作數據庫的邏輯,防止緩存擊穿

在這里插入圖片描述

操作鎖的代碼:

核心思路就是利用redis的setnx方法來表示獲取鎖,該方法含義是redis中如果沒有這個key,則插入成功,返回1,在stringRedisTemplate中返回true, 如果有這個key則插入失敗,則返回0,在stringRedisTemplate返回false,我們可以通過true,或者是false,來表示是否有線程成功插入key,成功插入的key的線程我們認為他就是獲得到鎖的線程。


private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}private void unlock(String key) {stringRedisTemplate.delete(key);
}

操作代碼:

 public Shop queryWithMutex(Long id)  {String key = CACHE_SHOP_KEY + id;// 1、從redis中查詢商鋪緩存String shopJson = stringRedisTemplate.opsForValue().get("key");// 2、判斷是否存在if (StrUtil.isNotBlank(shopJson)) {// 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判斷命中的值是否是空值if (shopJson != null) {//返回一個錯誤信息return null;}// 4.實現緩存重構//4.1 獲取互斥鎖String lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);// 4.2 判斷否獲取成功if(!isLock){//4.3 失敗,則休眠重試Thread.sleep(50);return queryWithMutex(id);}//4.4 成功,根據id查詢數據庫shop = getById(id);// 5.不存在,返回錯誤if(shop == null){//將空值寫入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);//返回錯誤信息return null;}//6.寫入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_NULL_TTL,TimeUnit.MINUTES);}catch (Exception e){throw new RuntimeException(e);}finally {//7.釋放互斥鎖unlock(lockKey);}return shop;}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167369.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167369.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167369.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

QOverload獲取重載的信號

QOverload獲取重載的信號 多個信號或者函數同名&#xff0c;但是不同參數&#xff0c;也就是存在重載 可以使用QOverload獲取指定的重載函數 QOverload<int>::of(&QComboBox::currentIndexChanged)上面的代碼就是用來獲取參數為int的那個函數

【Spring篇】JDK動態代理

目錄 什么是代理&#xff1f; 代理模式 動態代理 Java中常用的代理模式 問題來了&#xff0c;如何動態生成代理類&#xff1f; 動態代理底層實現 什么是代理&#xff1f; 顧名思義&#xff0c;代替某個對象去處理一些問題&#xff0c;謂之代理&#xff0c;那么何為動態&a…

短視頻賬號矩陣系統saas化批量管理部署搭建/技術

一、短視頻矩陣系統建模----技術api接口--獲取用戶授權 技術文檔分享&#xff1a; 本系統采用MySQL數據庫進行存儲&#xff0c;數據庫設計如下&#xff1a; 1.用戶表&#xff08;user&#xff09;&#xff1a; - 用戶ID&#xff08;user_id&#xff09; - 用戶名&#xff08;…

SELinux零知識學習二十七、SELinux策略語言之類型強制(12)

接前一篇文章:SELinux零知識學習二十六、SELinux策略語言之類型強制(11) 二、SELinux策略語言之類型強制 4. 類型規則 類型規則在創建客體或在運行過程中重新標記時指定其默認類型。在策略語言中定義了兩個類型規則: type_transtition在域轉換過程中標記行為發生時以及創…

詳解Vue中的computed和watch

詳解Vue中的computed和watch 前言原理computedcomputed特點computed有幾種創建方式應用 WatchWatch有幾種創建方式Watch主要內容Watch特性應用場景 computed和Watch區別 前言 在Vue當中&#xff0c;watch和computed都可以實現監聽的效果&#xff0c;本文主要是圍繞watch和comp…

【理解ARM架構】操作寄存器實現UART | 段的概念 | IDE背后的命令

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;專欄&#xff1a;《理解ARM架構》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交給時間&#xff01; 目錄 &#x1f360;操作寄存器實現UART&#x1f35f;UART原理&#x1f35f;編程 &#x1f360;…

python——第十二天

內置模塊或者其他模塊學習方式&#xff1a; dir help os模塊負責程序與操作系統的交互&#xff0c;提供了訪問操作系統底層的接口&#xff1b;即os模塊提供了非常豐富的方法用來處理文件和目錄。 os&#xff1a; os.path 遍歷C盤代碼 import os from os import path def …

修改YOLOv5的模型結構第三彈

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 | 接輔導、項目定制&#x1f680; 文章來源&#xff1a;K同學的學習圈子 文章目錄 任務任務拆解 開始修改C2模塊修改yolo.py修改模型配置文件 模型訓練 上次已…

【工具使用】Keil工具的使用——常用配置介紹

Keil調試具體教程學習 目錄 ???????Keil調試具體教程學習 常用功能總結 &#xff08;2&#xff09;目標設置&#xff08;Target&#xff09; ①設置晶振頻率 ②跨模塊優化選項 ③微庫選項 &#xff08;3&#xff09;輸出設置&#xff08;Output&#xff09; ①…

插入排序(形象類比)

最近在看riscv手冊的時候&#xff0c;里面有一段代碼是插入排序&#xff0c;但是單看代碼的時候有點迷&#xff0c;沒看懂咋操作的&#xff0c;后來又查資料復習了一下&#xff0c;最終才把代碼看明白&#xff0c;所以寫篇博客記錄一下。 插入排序像打撲克牌 這是我聽到過比較形…

list的總結

目錄 1.什么是list 1.1list 的優勢和劣勢 優勢&#xff1a; 劣勢&#xff1a; 2.構造函數 2.1 default (1) 2.2 fill (2) 2.3 range (3) 2.4 copy (4) 3.list iterator的使用 3.1. begin() 3.2. end() 3.3迭代器遍歷 4. list容量函數 4.1. empty() 4.2. siz…

語音合成綜述Speech Synthesis

一、語音合成概述 語音信號的產生分為兩個階段&#xff0c;信息編碼和生理控制。首先在大腦中出現某種想要表達的想法&#xff0c;然后由大腦將其編碼為具體的語言文字序列&#xff0c;及語音中可能存在的強調、重讀等韻律信息。經過語言的組織&#xff0c;大腦通過控制發音器…

正整數分解

題目編號&#xff1a;Exp08-Basic01&#xff0c;GJBook3-12-05 題目名稱&#xff1a;正整數分解 題目描述&#xff1a;正整數n&#xff0c;按第一項遞減的順序依次輸出其和等于n的所有不增的正整數和式。 輸入&#xff1a;一個正整數n&#xff08;0<n≤15&#xff09;。 …

qRT-PCR相對定量計算詳解qPCR相對定量計算方式——2^-(??Ct) deta t

做完轉錄組分析之后&#xff0c;一般都要求做qRT-PCR來驗證二代測序得到的轉錄本表達是否可靠。熒光定量PCR是一種相對表達定量的方法&#xff0c;他的計算方法有很多&#xff0c;常用的相對定量數據分析方法有雙標曲線法&#xff0c;ΔCt法&#xff0c;2^-ΔΔCt法(Livak法)&a…

順序表基本操作全面解析

文章目錄 1.線性表2.順序表分類2.1 靜態順序表2.2 動態順序表 3. 順序表各接口實現1. 定義結構體(Seqlist)2. 結構體初始化(SLInit)3.檢查容量 (SLCheckCapacity)4.打印數據 (SLPrintf)5.插入操作5.1 從數據頭部插入(SLPushFront)5.2 從數據尾部插入(SLPushBack)5.3 從任意下標…

100天精通Python(可視化篇)——第106天:Pyecharts繪制多種炫酷桑基圖參數說明+代碼實戰

文章目錄 專欄導讀一、桑基圖介紹1. 桑基圖是什么?2. 桑基圖應用場景?二、桑基圖配置選項1. 導包2. add函數3. 分層設置三、桑基圖基礎1. 普通桑基圖2. 修改標簽位置3. 修改節點布局方向4、月度開支桑基圖書籍推薦專欄導讀 ????本文已收錄于《100天精通Python從入門到就…

線性表之順序表

文章目錄 主要內容一.順序表1.插入操作&#xff1a;代碼如下&#xff08;示例&#xff09;: 2.刪除操作&#xff1a;代碼如下&#xff08;示例&#xff09;: 3.按值查找&#xff1a;代碼如下&#xff08;示例&#xff09;: 總結 主要內容 順序表 預備知識 定義&#xff1a; 線…

GEE:基于 Landst 遙感數據計算的 kNDVI 下載 APP

作者&#xff1a;CSDN _養樂多_ 本文記錄了在Google Earth Engine&#xff08;GEE&#xff09;平臺中&#xff0c;使用 Landsat 遙感數據計算并且下載 kNDVI 的應用 APP 鏈接&#xff0c;并介紹該 APP 的使用方法和步驟。該APP可以為用戶展示 NDVI 和 kNDVI 的遙感影像&#…

抽象類, 接口, Object類 ---java

目錄 一. 抽象類 1.1 抽象類概念 1.2 抽象類語法 1.3 抽象類特性 1.4 抽象類的作用 二. 接口 2.1 接口的概念 2.2 語法規則 2.3 接口的使用 2.4 接口間的繼承 2.5 抽象類和接口的區別 三. Object類 3.1 toString() 方法 3.2 對象比較equals()方法 3.3 hash…

免費獲取GPT-4的五種工具

不可否認&#xff0c;由OpenAI帶來的GPT-4已是全球最受歡迎的、功能最強大的大語言模型&#xff08;LLM&#xff09;之一。大多數人都需要使用ChatGPT Plus的訂閱服務去訪問GPT-4。為此&#xff0c;他們通常需要每月支付20美元。那么問題來了&#xff0c;如果您不想每月有這筆支…