Redis 實戰 - 緩存異常及解決方案

在這里插入圖片描述

文章目錄

  • 概述
  • 一、緩存穿透
    • 1.1 緩存穿透是什么
    • 1.2 解決方案
  • 二、緩存擊穿
    • 2.1 緩存擊穿是什么
    • 2.2 解決方案
  • 三、緩存雪崩
    • 3.1 緩存雪崩是什么
    • 3.2 解決方案
  • 四、拓展
    • 4.1 緩存預熱
    • 4.2 緩存降級
  • 五、結語

把今天最好的表現當作明天最新的起點…….~

概述

??在實際的業務場景中,Redis 一般和其他數據庫搭配使用,比如和關系型數據庫 MySQL 配合使用,用來減輕后端數據庫的壓力。Redis 會把 MySQL 中經常被查詢的數據緩存起來,比如熱點數據,這樣當用戶來訪問的時候,就不需要到 MySQL 中去查詢,而是直接獲取 Redis 中的緩存數據,從而降低了后端數據庫的讀取壓力。如果說用戶查詢的數據在 Redis 沒有找到,那么用戶的查詢請求就會被轉到 MySQL 數據庫。當 MySQL 將查詢到的數據返回給客戶端時,同時也會將數據緩存到 Redis 中,這樣用戶再次讀取時,就可以直接從 Redis 中獲取數據。流程圖如下所示:
在這里插入圖片描述
??在使用 Redis 作為緩存數據庫的過程中,有時也會遇到一些棘手問題,比如常見緩存穿透、緩存擊穿和緩存雪崩等問題,如下圖所示。本文中將對這些問題做簡單地說明,并且提供有效的解決方案。

Redis 緩存異常
緩存穿透
緩存擊穿
緩存雪崩

一、緩存穿透

1.1 緩存穿透是什么

??一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,那么就去數據庫去查找。當用戶查詢某個數據時,Redis 中不存在該數據,也就是緩存沒有命中,此時查詢請求就會轉向數據庫,結果發現數據庫中也不存在該數據,數據庫只能返回一個空對象(相當于進行了兩次無用的查詢)。用戶拿不到數據時,就會一直發請求查詢數據庫,這樣會對數據庫的訪問造成很大的壓力。如果這種類請求非常多,或者用戶利用這種請求進行惡意攻擊,就會給數據庫造成很大壓力,甚至于崩潰,這種現象就叫緩存穿透。
在這里插入圖片描述

??這種現象的原因其實很好理解,當客戶端訪問不存在的數據時,先請求 Redis,但是此時 Redis 中并沒有數據,此時會訪問到數據庫,但是數據庫中也沒有數據,這個數據穿透了緩存,直擊數據庫。我們都知道數據庫能夠承載的并發不如 Redis 這么高,如果大量的請求同時過來訪問這種不存在的數據,這些請求就都會訪問到數據庫。

1.2 解決方案

??簡單的解決方案就是當Redis、數據庫中都沒有值返回空對象時, 可以在 Redis 中存放一個空值,同時為其設置一個過期時間。這樣,當用戶再次發起相同請求訪問這個不存在的數據,那么就會從緩存中拿到一個空對象,用戶的請求被阻斷在了緩存層。這樣就可以減少重復查詢空值引起的系統壓力增大,從而從而保護了后端數據庫。示例代碼如下:

private String queryMessager(String key){// 從緩存中獲取數據String message = getFromCache(key);// 如果緩存中沒有 從數據庫中查找if(StringUtils.isBlank(message)){message = getFromDb(key);// 如果數據庫中也沒有數據 就設置短時間的緩存if(StringUtils.isBlank(message)){// 設置緩存時間(緩存的key,緩存的值,失效時間:單位秒)redisClient.setNxEx(key,null,60);} else {redisClient.setNxEx(key,message,1800);}}return message;
}

??這種做法雖然優化了緩存穿透問題,但也存在一些問題。雖然請求進不了數據庫,但是會占用 Redis 的緩存空間。而大量的空緩存導致資源的浪費,也有可能導致 Redis 和數據庫中的數據不一致。

二、緩存擊穿

2.1 緩存擊穿是什么

??我們的業務通常會有幾個數據會被頻繁地訪問,比如秒殺活動,這類被頻地訪問的數據被稱為熱點數據。比如某個熱點數據,它無時無刻都在接受大量的并發訪問,如果在某一時刻忽然過期了,此時大量的請求訪問了該熱點數據,就無法從緩存中讀取,導致大量的并發請求直接訪問數據庫,就像在一個完好無損的桶上鑿開了一個洞,引起數據庫壓力瞬間增大,這種現象被稱為緩存擊穿。
在這里插入圖片描述

??緩存擊穿一般出現在高并發系統中,是大量并發用戶同時請求到緩存中沒有但數據庫中有的數據,也就是同時讀緩存沒讀到數據,又同時去數據庫去取數據。由于請大量請求同時過來,來不及更新緩存就全部打到數據庫那邊,引起數據庫壓力瞬間增大。

2.2 解決方案

  • 將熱點數據設置加上互斥鎖
    • 此方法只允許一個線程重建緩存,其他線程等待重建緩存的線程執行完,重新從緩存獲取數據即可。當第一個數據庫查詢請求發起后,就將緩存中該數據上鎖;此時到達緩存的其他查詢請求將無法查詢該字段,從而被阻塞等待;當第一個請求完成數據庫查詢,并將數據更新值緩存后,釋放鎖;此時其他被阻塞的查詢請求將可以直接從緩存中查到該數據。

      private ReentrantLock reentrantLock = new ReentrantLock();
      public static String getData(String key) throws InterruptedException {// 從 Redis 查詢數據String result = getDataByKey(key);// 參數校驗if (StringUtils.isBlank(result)) {// 獲取鎖if (reentrantLock.tryLock()) {// 去數據庫查詢result = getDataByDB(key);// 校驗if (StringUtils.isNotBlank(result)) {// 搞進緩存setDataToKey(key, result);}// 釋放鎖,正常會在finally中釋放reentrantLock.unlock();} else {// 稍等一下Thread.sleep(100L);result = getData(key);}}return result;
      }
      
    • 當某一個熱點數據失效后,只有第一個數據庫查詢請求發往數據庫,其余所有的查詢請求均被阻塞,從而保護了數據庫。但是,由于采用了互斥鎖,其他請求將會阻塞等待,可能會存在死鎖和線程池阻塞的風險,此時系統的吞吐量將會下降,這需要結合實際的業務考慮是否允許這么做。

  • 將熱點數據設置為永遠不過期
    • 當向緩存中存儲這些數據的時候,可以將他們的緩存失效時間錯開,這樣能夠避免同時失效。如在一個基礎時間上加/減一個隨機數,從而將這些緩存的失效時間錯開。

      private void setRandomTimeForReidsKey(String redisKey,String value){//隨機函數Random rand = new Random();//隨機獲取30分鐘內(30*60)的隨機數int times = rand.nextInt(1800);//設置緩存時間(緩存的key,緩存的值,失效時間:單位秒)redisClient.setNxEx(redisKey,value,times);
      }
      
    • 這種方案由于沒有設置真正的過期時間,實際上已經不存在熱點key產生的一系列危害,但是會存在數據不一致的情況,同時代碼復雜度會增大。

三、緩存雪崩

3.1 緩存雪崩是什么

??通常,為了保證 Redis 中的數據與數據庫中的數據一致性,通常會給 Redis 里的數據設置過期時間。當緩存數據過期后,用戶訪問的數據如果不在 Redis 里,業務系統需要重新生成緩存,因此就會訪問數據庫,并將數據更新到 Redis 里,這樣后續請求都可以直接命中緩存。

應用 Redis 數據庫 從緩存讀取數據 緩存過期 緩存不存在 從數據庫讀取數據 返回數據庫中的數據 將數據加載到緩存 應用 Redis 數據庫

??但當Redis 故障宕機或者緩存中大批量的數據同一時間過期(失效),而此時數據訪問量又非常大,無法在 Redis 中處理,于是全部直接訪問數據庫,從而導致數據庫壓力突然暴增,嚴重時甚至可能導致數據庫崩潰。就像雪崩一樣,引發一系列連鎖效應,從而波及整個系統崩潰,這種現象被稱為緩存雪崩。如下圖所示:
在這里插入圖片描述

??假設當時每秒6000個請求,本來緩存在可以扛住每秒5000個請求,但是緩存當時所有的Key都失效了。此時1秒6000個請求全部落數據庫,數據庫必然扛不住,可能DBA都沒反應過來就直接掛了,即便是重啟數據庫,但是數據庫立馬又被新的流量給打死了。以秒殺系統為例,圖示說明:
在這里插入圖片描述

??它和緩存擊穿不同,緩存擊穿是在并發量特別大時,某一個熱點 key 突然過期,而緩存雪崩則是大量的 key 同時過期,因此它們根本不是一個量級。

3.2 解決方案

??出現上述情況的常見原因主要有以下兩點:

  • 大量緩存數據同時過期,導致本應請求到緩存的需重新從數據庫中獲取數據。
  • Redis 本身出現故障,無法處理請求,那自然會再請求到數據庫那里。

??針對上面出現故障的情況,可以從以下幾點出發解決:

  • 事前:構建高可用的集群,實現主 Redis 實例掛掉后,能有其他從庫快速切換為主庫,繼續提供服務,避免全盤崩潰。
  • 事中:在往 Redis 存數據時,可以通過隨機、微調、均勻設置等方式設置過期時間,這樣可以保證數據不會在同一時間大面積失效。如果事情已經發生了,那就要為了防止數據庫被大量的請求搞崩潰,可以采用服務熔斷或者請求限流的方法。當然服務熔斷相對粗暴一些,停止服務直到redis服務恢復;而請求限流相對溫和一些,保證一些請求可以處理,不過還是看具體業務情況選擇合適的處理方案。
  • 事后:redis持久化,一旦重啟,自動從磁盤上加載數據,快速恢復緩存數據。

四、拓展

4.1 緩存預熱

??緩存預熱就是系統上線前后,將相關的緩存數據直接加載到緩存系統中去,而不依賴用戶。這樣就可以避免在用戶請求的時候,先查詢數據庫,然后再將數據緩存的問題。用戶直接查詢事先被預熱的緩存數據,這樣可以避免那么系統上線初期,對于高并發的流量,都會訪問到數據庫中, 對數據庫造成流量的壓力。根據數據不同量級,可以有以下幾種做法:

  • 數據量不大:項目啟動的時候自動進行加載。
  • 數據量較大:后臺定時刷新緩存。
  • 數據量極大:只針對熱點數據進行預加載緩存操作。

4.2 緩存降級

??緩存降級是指當緩存失效或緩存服務出現問題時,為了防止緩存服務故障,導致數據庫跟著一起發生雪崩問題,所以也不去訪問數據庫,但因為一些原因,仍然想要保證服務還是基本可用的,雖然肯定會是有損服務。因此,對于不重要的緩存數據,我們可以采取服務降級策略。一般做法有以下兩種:

  • 直接訪問內存部分的數據緩存。
  • 直接返回系統設置的默認值。

五、結語

??Redis 緩存異常會面臨的三個問題:緩存雪崩、擊穿和穿透。其中,緩存雪崩和緩存擊穿主要原因是數據不在緩存中,而導致大量請求訪問了數據庫,數據庫壓力驟增,容易引發一系列連鎖反應,導致系統奔潰。不過,一旦數據被重新加載回緩存,應用又可以從緩存快速讀取數據,不再繼續訪問數據庫,數據庫的壓力也會瞬間降下來。因此,緩存雪崩和緩存擊穿應對的方案比較類似。而緩存穿透主要原因是數據既不在緩存也不在數據庫中。因此,緩存穿透與緩存雪崩、擊穿應對的方案不太一樣。
??Redis 緩存在互聯網中至關重要,可以很大的提升系統效率。 本文介紹的緩存異常以及解決思路有可能不夠全面,但也提供相應的解決思路和代碼大體實現,希望可以為大家提供一些遇到緩存問題時的解決思路。如果有不足的地方,也請幫忙指出,大家共同進步。
在這里插入圖片描述

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

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

相關文章

YoloV8改進策略:Neck層改進、注意力改進|HCANet全局與局部的注意力模塊CAFM|二次創新|即插即用

yolov9-c summary: 620 layers, 52330674 parameters, 0 gradients, 245.5 GFLOPsClass Images Instances P R mAP50 mAP50-95: 100%|██████████| 15/15 00:06all 230 1412 0.917 0.985 0.99 0.735…

實現自動化巡檢多臺交換機并將輸出信息保存到文本文件中

為了實現自動化巡檢多臺交換機并將輸出信息保存到文本文件中,可以擴展之前的 SSHInspectionTool 類,使其能夠處理多臺交換機的連接和命令執行。我們可以使用多線程來并行處理多個 SSH 連接,以提高效率。 目錄 一、導入依賴包 二、編寫Java類 (1)SSH.java (2)SSHIns…

LeetCode 第131場雙周賽個人題解

100309. 求出出現兩次數字的 XOR 值 原題鏈接 求出出現兩次數字的 XOR 值 - 力扣 (LeetCode) 競賽 思路分析 簽到題,一次遍歷 AC代碼 class Solution:def duplicateNumbersXOR(self, nums: List[int]) -> int:cnt Counter(nums)res 0st set(nums)for x …

Python基礎學習筆記(七)——元組

目錄 一、一維元組的介紹、創建與修改二、組合的基本操作1. 遍歷2. 取長度3. 取最值4. 打包5. 批處理5.1 map()函數5.2 lambda 表達式5.3 lambda 表達式 map()函數 一、一維元組的介紹、創建與修改 元組(tuple),一種不可變、有序、可重復的數…

SpringBoot如何開啟注解的形式,使用Redis Cache

Spring Cache 是一個框架,實現了基于注解的緩存功能,只需要簡單的添加注解,就能實現緩存功能。 Spring Cache 提供了一層抽象,底層可以切換不同的緩存實現,例如:Redis、EHCache、Caffeine等 步驟&#xf…

【大模型】Spring AI對接ChatGpt使用詳解

目錄 一、前言 二、spring ai介紹 2.1 什么是Spring AI 2.2 Spring AI 特點 2.3 Spring AI 為開發帶來的便利 2.4 Spring AI應用領域 2.4.1 聊天模型 2.4.2 文本到圖像模型 2.4.3 音頻轉文本 2.4.4 嵌入大模型使用 2.4.5 矢量數據庫支持 2.4.6 用于數據工程ETL框架 …

2024-05-22 VS2022使用modules

點擊 <C 語言編程核心突破> 快速C語言入門 VS2022使用modules 前言一、準備二、使用其一, 用VS installer 安裝模塊:第二個選項就是, 與你的代碼一同編譯std模塊, 這個非常簡單, 但是也有坑. 總結 前言 要解決問題: 使用VS2022開啟modules. 想到的思路: 跟著官方文檔整…

Java進階學習筆記19——內部類

1、 內部類&#xff1a; 是類中五大成分之一&#xff08;成員變量、方法、構造函數、內部類、代碼塊&#xff09;&#xff0c;如果一個類定義在另一個 類的內部&#xff0c;這個類就是內部類。 場景&#xff1a;當一個類的內部&#xff0c;包含了一個完整的事物&#xff0c;且…

Android ART 虛擬機簡析

源碼基于&#xff1a;Android U 1. prop 名稱選項名稱heap 變量名稱功能 dalvik.vm.heapstartsize MemoryInitialSize initial_heap_size_ 虛擬機在啟動時&#xff0c;向系統申請的起始內存 dalvik.vm.heapgrowthlimit HeapGrowthLimit growth_limit_ 應用可使用的 max…

Scikit-Learn樸素貝葉斯

Scikit-Learn樸素貝葉斯 1、樸素貝葉斯1.1、貝葉斯分類1.2、貝葉斯定理1.3、貝葉斯定理的推導1.4、樸素貝葉斯及原理1.5、樸素貝葉斯的優缺點2、Scikit-Learn樸素貝葉斯2.1、Sklearn中的貝葉斯分類器2.2、Scikit-Learn樸素貝葉斯API2.3、Scikit-Learn樸素貝葉斯實踐(新聞分類與…

Python——文件操作相關

1. 讀文件方式 第一種 有規律的名稱 第二種 無規律的名稱 2. 文件名稱 用 “{:05d}” 來規范輸出數字所占位數&#xff0c;例如&#xff1a; for i in range(100):gt_file Reference/ image "{:05d}".format(i) .jpgprint(gt_file)輸出&#xff1a; ... Re…

爬山算法的詳細介紹

目錄 &#x1f349;概述 &#x1f349; 步驟 &#x1f349; 優缺點 &#x1f348;優點 &#x1f348;缺點 &#x1f348;應對策略 &#x1f349;示例 &#x1f348;旅行商問題 &#x1f34d;步驟 &#x1f34d;分解代碼 &#x1f34e;包含頭文件 &#x1f34e;定義函…

Cortex-M3的SysTick 定時器

目錄 概述 1 SysTick 定時器 1.1 SysTick 定時器功能介紹 1.2 SysTick 定時器功能實現 1.3 SysTick在系統中的作用 2 SysTick應用的實例 2.1 建立異常服務例程 2.2 使能異常 2.3 鬧鐘功能 2.4 重定位向量表 2.5 消滅二次觸發 3 SysTick在FreeRTOS中的應用 3.1 STM…

【代碼】結構體

哈嘍大家好&#xff0c;我是學霸小羊&#xff0c;今天講講結構體。 先看例題&#xff1a; 例1.老師給了小楊一份同學們的考試成績&#xff0c;包括語數英三科&#xff0c;老師讓小明按照總分排序&#xff0c;請你幫幫他吧&#xff01; 輸入數據&#xff1a; 第1行 學生總人…

在docker中運行SLAM十四講程序

《十四講》的示例程序依賴比較多&#xff0c;而且系統有點舊。可以在容器中運行。 拉取鏡像 docker pull ddhogan/slambook:v0.1這個docker對應的github&#xff1a;HomeLH/slambook2-docker 拉下來之后&#xff0c;假如是Windows系統&#xff0c;需要使用XLaunch用于提供X11…

面試大雜燴之kafka

面試這個領域最近環境不行&#xff0c;所以卷起來流量挺大 關于K8s 其實看我之前的博客&#xff0c;k8s剛有點苗頭的時候我就研究過&#xff0c;然后工作的時候間接接觸 也自己玩過 但是用的不多就忘記了&#xff0c;正苦于不知道寫什么&#xff0c;水一篇 用來面試應該是夠了…

C++ | Leetcode C++題解之第111題二叉樹的最小深度

題目&#xff1a; 題解&#xff1a; class Solution { public:int minDepth(TreeNode *root) {if (root nullptr) {return 0;}queue<pair<TreeNode *, int> > que;que.emplace(root, 1);while (!que.empty()) {TreeNode *node que.front().first;int depth que…

VC編譯sample_onnx_mnist提示無法打開輸入文件cudnn.lib

出現錯誤 LNK1181 無法打開輸入文件“C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.1\lib\x64\cudnn.lib” 解決辦法&#xff1a;下載cudnn&#xff0c;NVIDIA cuDNN | NVIDIA Developer 拷貝相應的文件到CUDA安裝的目錄下。 VC編譯libtorch提示無法打開輸入文件cu…

huggingface 筆記:PretrainModel

1 from_pretrained 從預訓練模型配置中實例化一個 PyTorch 預訓練模型默認情況下&#xff0c;模型使用 model.eval() 設置為評估模式&#xff08;Dropout 模塊被禁用&#xff09; 要訓練模型&#xff0c;應該首先使用 model.train() 將其設置回訓練模式 1.1 主要參數 pretra…

java 子類繼承父類

為什么需要繼承 我現在要有兩個類一個 一個是小學生&#xff0c;一個是大學生 代碼 小學生 package b; public class encapsulatio{public String name;public int age;public double score;public void setscore (double score) {this.scorescore;}public void testing() {S…