Redis - 緩存場景

學習資料

學習的黑馬程序員嗶站項目黑馬點評,用作記錄和探究原理。

Redis緩存

緩存 :就是數據交換的緩沖區,是存儲數據的臨時地方,讀寫性能較高
緩存常見的場景:

  • 數據庫查詢加速:通過將頻繁查詢的數據緩存起來,減少對數據庫的直接訪問,提高查詢效率。
  • 網頁內容緩存:將動態生成的網頁內容緩存起來,減少服務器生成頁面的次數,提高網頁響應速度。
  • 會話數據緩存:將用戶會話數據緩存起來,支持分布式系統中的會話共享。
  • 隊列系統:將任務隊列緩存起來,支持高效的任務調度和執行。
    在這里插入圖片描述
使用Redis緩存的流程

實際上,讀取數據庫是很消耗資源的。假設現在有一大批用戶短時間內請求數據庫,數據庫此時將面臨巨大的壓力。為了緩解這種壓力,可以使用Redis來緩存頻繁訪問的數據,減少對數據庫的直接訪問。
在這里插入圖片描述
具體流程如下:
檢查緩存:首先檢查Redis緩存中是否存在所需數據。

如果緩存中存在數據,則直接返回數據給用戶,結束流程。
如果緩存中不存在數據,則繼續下一步。

查詢數據庫:從數據庫中查詢所需數據。

查詢到數據后,將數據返回給用戶,同時將數據寫入Redis緩存,以便下次快速訪問。
如果未查詢到數據,則返回空或相應的提示信息。

更新緩存:當數據庫中的數據發生變化時,需要同步更新Redis緩存中的數據,確保數據的一致性。通常可以使用以下兩種策略:

主動更新:在數據庫更新的同時,主動更新緩存中的數據。
被動更新:當檢測到緩存中的數據已失效時,再次從數據庫中查詢數據并更新緩存。

更新策略問題

為了保證緩存一致問題,我們在選擇更新策略的時候一般選擇主動更新的策略,

被動更新不一致問題

因為被動更新會出現不一致情況,比如說我們現在將一個店鋪信息存入數據庫,策略選擇的被動策略,但是查詢店鋪信息我們選擇的是使用Redis緩存,此時永遠不回去更新這個店鋪,因為我頁面呈遞不出來這個店鋪信息,怎么能去請求他然后查詢數據庫更新呢?這就出現不一致的情況了。
對于這種情況再進行一個說明,我這邊說的場景就是說我們把這個商品的列表信息存入Redis,當添加一個新的商品之后,緩存中沒有,那么前端中就指定不會有,此時就很尷尬的一個場景。但是說一般不會這樣玩,一般只是把商品詳情信息存入Redis,商品列表還是查詢數據庫,畢竟不是很多,并且這樣的話避免出現不同步問題。

主動更新 緩存更新時機選擇

主動更新的時候時機選擇也是比較重要的,當我更新數據庫一條消息的時候,此時我是選擇先將數據存入數據庫還是說先將Redis中的數據刪除? 或者我向數據庫添加一個消息,我是先存入數據庫,還是先存入Redis中呢?
我們來看一下這張圖

先刪除緩存再更新數據庫

在這里插入圖片描述
場景先刪除緩存,然后操作更新數據庫,這個時候我們面臨一個問題,由于就是說第一次請求肯定是先請求緩存中的,當緩存沒有的時候此時請求數據庫,我們假設第一個用戶A此時更新了數據庫中一件商品的信息,選擇先把緩存中舊的信息刪除,然后將數據庫更新,那么此時用戶A更新數據庫的時候,用戶B來訪問這個信息,先看緩存,緩存中沒有,那么此時肯定得查詢數據庫,數據庫中還沒更新成功,那么這個數據查詢依舊是舊的值,這樣不就出現問題了嗎,這樣的話就出現一個場景,由于就是更新數據庫耗時久,那么這個時候出現大量請求這個內容,那么持續請求數據庫,會造成壓力,二就是此時數據也不一致。

先更新數據庫,再刪除緩存。

在這里插入圖片描述
在處理緩存和數據庫更新時,我們選擇先更新數據庫,然后刪除緩存。這樣做有幾個優勢:
降低數據庫查詢壓力:
當用戶訪問該信息時,緩存中仍然是舊的信息。雖然這意味著短時間內用戶可能獲取到的是舊數據,但由于緩存命中,減少了對數據庫的直接查詢,降低了數據庫的查詢壓力。
確保數據一致性:
一旦數據庫更新完成,再刪除緩存。此時,緩存被清除,下一次用戶訪問該信息時,會從數據庫讀取最新的數據并重新加載到緩存中。這種方式保證了數據的一致性,確保緩存中不會出現過時的數據。

針對緩存不一致問題我了解的解決方案有以下幾種:
  1. 延遲雙刪策略(Double Deletion with Delay)
    在更新數據庫后,通過延遲再次刪除緩存以確保數據的一致性。
// 更新數據庫
updateDatabase();
// 刪除緩存
deleteCache();
// 延遲一段時間后再刪除緩存
Thread.sleep(500);  // 延遲時間根據具體場景調整
deleteCache();
  1. 互斥鎖機制(Mutex Locking Mechanism)
    使用分布式鎖來避免多個請求同時更新數據庫或緩存。確保只有一個請求可以訪問數據庫并更新緩存。
String value = getCache(key);
if (value == null) {if (lock(key)) {try {value = queryDatabase();setCache(key, value);} finally {unlock(key);}} else {// 等待鎖釋放后重試Thread.sleep(50);  // 重試時間根據具體場景調整value = getCache(key);}
}
return value;
  1. 緩存預熱(Cache Warming)
    在應用啟動時或緩存失效時,預先加載常用的數據到緩存中,減少緩存穿透的概率。
// 在應用啟動時加載常用數據到緩存
loadCommonDataToCache();
  1. 讀寫分離(Read-Write Separation)
    將讀操作與寫操作分離,寫操作使用主數據庫,讀操作使用從數據庫,減輕數據庫壓力。
// 寫操作使用主數據庫
updateMainDatabase();// 讀操作使用從數據庫
value = queryReadReplicaDatabase();

緩存場景會出現的問題

緩存穿透

是指客戶端請求的數據在緩存中和數據庫中都不存在,這樣緩存永遠不會生效,這些請求都會打到數據庫。常見的解決方案有兩種。

  • 緩存空對象
    也就是說我們此時查詢數據庫發現這個內容是空的,我們避免下一次有人把繼續訪問這個空的內容,然后造成服務器壓力,那么此時我們把這個空的內容緩存到Redis中,此時當再次請求這個內容的時候,請求的是Redis中的內容,對服務器壓力會降低很多。
步驟:

客戶端請求數據。
檢查緩存,發現沒有命中。
查詢數據庫,發現數據不存在。
將空對象(如null或者特殊標識)緩存到Redis中,并設置一個合理的過期時間。
當再次請求這個內容時,直接從Redis中獲取空對象,減少數據庫壓力。
在這里插入圖片描述

  • 布隆過濾
    布隆過濾器是一種概率型數據結構,可以高效地判斷某個元素是否在一個集合中。通過將所有可能存在的緩存鍵值存儲在布隆過濾器中,可以快速判斷某個請求是否是無效的(即數據庫中也不存在),從而減少對數據庫的查詢。
步驟:

初始化布隆過濾器,將所有可能存在的鍵值添加到過濾器中。
客戶端請求數據時,先檢查布隆過濾器。
如果布隆過濾器判斷數據不存在,直接返回空結果,避免查詢數據庫。
如果布隆過濾器判斷數據可能存在,再查詢緩存和數據庫。
示例代碼:

// 初始化布隆過濾器,并添加所有可能存在的鍵值
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000000);
bloomFilter.putAll(getAllPossibleKeys());String key = getRequestKey();
if (!bloomFilter.mightContain(key)) {// 布隆過濾器判斷數據不存在,直接返回空結果return null;
}String value = getCache(key);
if (value == null) {value = queryDatabase(key);if (value == null) {// 數據不存在,緩存空對象setCache(key, "NULL", 300);  // 300秒過期時間,可根據具體情況調整} else {// 數據存在,緩存實際數據setCache(key, value);}
}
if ("NULL".equals(value)) {// 返回數據不存在的響應return null;
}
return value;
緩存穿透的解決方案
  • 緩存null值
  • 布隆過濾
  • 增強id的復雜度,避免被猜測id規律
  • 做好數據的基礎格式校驗
  • 加強用戶權限校驗

緩存雪崩

緩存雪崩主要是由于一段時間內,緩存的key值全部失效,也就是緩存期結束,導致大量請求到達數據庫,帶來巨大的壓力。
那么這個場景的解決方案也很簡單。
在這里插入圖片描述

緩存擊穿

緩存擊穿被稱為熱點key 問題,就是一種被高并發訪問并且緩存重建業務較復雜的key,失效了,大量請求訪問的瞬間給數據庫帶來巨大壓力
假設一家店突然推出一家新的菜品,但是說此時緩存過程中數據庫更新了,將舊的Redis緩存刪除之后,此時大量用戶訪問這個內容的話會出現一個場景,形成一個閉環。
在這里插入圖片描述
這樣不就出現大量請求數據庫的場景了嗎?我們要怎么避免這個內容呢
這個解決方案有兩種:

方案一:使用互斥鎖

相信大家在學習javase階段的多線程肯定面臨一個問題,也就是說多線程搶票問題,當不加鎖的情況下會出現超賣問題,加一個鎖一次只能搶一個,這樣會更好。這個位置也就是說更新緩存的時候加一個鎖,讓其他業務線程不進行更新操作。
但是說這個鎖應該選什么呢?現在既然在使用Redis,那么這個時候我們就直接選擇使用Redis中字符串類型的SETNX 作為互斥鎖。

方案二:使用邏輯過期

將Redis緩存中的內容設置一個邏輯過期字段,保證在讀取緩存時,可以判斷數據是否過期。這樣可以減少緩存穿透和擊穿問題。
具體步驟:
客戶端請求數據。
檢查緩存,獲取緩存數據和邏輯過期時間。
如果數據未過期,直接返回緩存數據。
如果數據已過期或緩存未命中:
啟動一個異步線程更新緩存。
返回舊數據或提示正在更新中。

緩存擊穿 - Java代碼解決

互斥鎖解決

互斥鎖這邊使用的鎖對象是Redis 中String類型的SETNX,由于其一個鍵只能賦值一次,這樣的話符合預期場景。
在這里插入圖片描述
這樣寫的話,會保證不會出現連續查詢數據庫,保證當一個值改變之后只更新一次數據庫操作。
其實也就是說,當我線程一更新的時候,我先判斷指定的鎖的key是否存在,存在將他設置,此時相當于線程一獲取了鎖,那么縣城二進入的時候便不可以獲取道這個鎖對象,那么線程二就需要等待,等待之后重試。直到緩存命中才結束。

場景:書寫接口查詢店鋪的詳細信息。其中id是店鋪的標識
    public Shop queryWithPassThrough(Long id) {//         店鋪在Redis中的key規范 同一前綴 + 店鋪idString key = RedisConstants.CACHE_SHOP_KEY + id;//         查詢redis緩存中 是否會有這個內容String s = stringRedisTemplate.opsForValue().get(key);//         存在 直接返回if (StrUtil.isNotBlank(s)) {return JSONUtil.toBean(s, Shop.class);}/**   既然不是null  那一定是  ""*   這樣的話我們需要進行 返回錯誤了 防止繼續去查詢數據庫*   這個是緩存穿透的一個防護手段 上面講解過了* */if (s != null) {return null;}// 實現緩存重建// 獲取互斥鎖String lockKey = RedisConstants.LOCK_SHOP_KEY + id;Shop byId = null;try {
//            獲取鎖 直接使用String中的setnx即可 判斷是否獲取成功boolean b = tryLocal(lockKey);
//            失敗if (!b) {// 休眠Thread.sleep(50);// 重試 重新調用遞歸queryWithPassThrough(id);}//         不存在 查詢數據庫byId = getById(id);//        不存在 返回錯誤if (byId == null) {/**   寫入空值內容 防止 持續 緩存穿透 造成服務器資源浪費*   時間設置成 2 min* */stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}//        數據庫存在//         將數據放入redis中/**   這個位置書寫一個超時設置 避免資源浪費*   主要是為了解決*   緩存不一致問題* */stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(byId), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//         返回數據} catch (Exception e) {throw new RuntimeException(e);} finally {
//            釋放鎖delLocal(lockKey);}
//        返回店鋪詳情return byId;}private boolean tryLocal(String key) {Boolean judge = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(judge);}private void delLocal(String key) {stringRedisTemplate.delete(key);}

邏輯刪除

邏輯刪除 ,也就是向儲存的數據中加一個字段,這個字段就是過期時間,這邊設置的實體是按照這種格式設計的內容。

package com.hmdp.utils;import lombok.Data;import java.time.LocalDateTime;/*
*   邏輯過期時間實體
* */
@Data
public class RedisData {private LocalDateTime expireTime;// 存入的數據private Object data;
}

我們緩存重建的時候,需要將邏輯過期時間重置,所以這個時候我們需要封裝一個方法來重置邏輯過期時間。

    public void saveShopToRedis(Long id, Long seconds) {
//        1.查詢店鋪數據Shop byId = getById(id);RedisData redisData = new RedisData();redisData.setData(byId);
//        2.封裝邏輯過期時間redisData.setExpireTime(LocalDateTime.now().plusSeconds(seconds));
//        3.寫入redisstringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}

邏輯過期代碼 - 其中緩存重建新開一個線程使用。

public Shop queryWithLogicalExpire(Long id) {String key = RedisConstants.CACHE_SHOP_KEY + id;
//         查詢redis緩存中 是否會有這個內容String s = stringRedisTemplate.opsForValue().get(key);
//         存在 直接返回if (StrUtil.isNotBlank(s)) {return JSONUtil.toBean(s, Shop.class);}/**   既然不是null  那一定是  ""*   這樣的話我們需要進行 返回錯誤了 防止繼續去查詢數據庫*   這個是緩存穿透的一個防護手段* */if (s != null) {return null;}RedisData bean = JSONUtil.toBean(s, RedisData.class);Shop shop = JSONUtil.toBean((JSONObject) bean.getData(), Shop.class);LocalDateTime localDateTime = bean.getExpireTime();if (localDateTime.isAfter(LocalDateTime.now())) {return shop;}String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean b = tryLocal(lockKey);if (b) {try {CACHE_REBUILD_EXCUTOR.submit(() -> {this.saveShopToRedis(id, RedisConstants.CACHE_SHOP_TTL);});} catch (Exception e) {throw new RuntimeException(e);} finally {
//            釋放鎖delLocal(lockKey);}}return shop;}

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

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

相關文章

【挖金子game】

如果您想要編寫一個簡單的“挖金子”游戲代碼&#xff0c;可以使用Python這樣的編程語言來實現。以下是一個簡單的Python代碼示例&#xff0c;用于創建一個基本的“挖金子”游戲&#xff1a; import random # 游戲設置 max_gold 10 # 最大金子數量 max_digs 5 # 最大挖掘…

數據驅動(Data-Driven)和以數據為中心(Data-Centric)的區別

一、什么是數據驅動&#xff1f; 數據驅動&#xff08;Data-Driven&#xff09;是在管理科學領域經常提到的名詞。數據驅動決策&#xff08;Data-Driven Decision Making&#xff0c;簡稱DDD&#xff09;是一種方法論&#xff0c;即在決策過程中主要依賴于數據分析和解釋&…

Java基礎學習:java中的基礎注解

在Java中&#xff0c;有一些內置的&#xff08;或稱為“基礎”&#xff09;注解&#xff08;annotation&#xff09;&#xff0c;這些注解在Java標準庫中定義&#xff0c;并且具有特定的用途。以下是一些主要的Java內置注解&#xff1a; Override&#xff1a; 用于表示一個方法…

Keras深度學習框架第二十七講:KerasTuner超參數優化基礎

1、超參數優化概念 1.1 什么是超參數優化 超參數調優&#xff0c;也稱為超參數優化或參數調優&#xff0c;是尋找學習算法或模型最佳超參數組合的過程。超參數是在訓練過程開始之前設置的參數&#xff0c;模型無法直接從數據中學習這些參數。它們控制著學習算法的行為&#x…

NDIS小端口驅動開發(二)

初始化微型端口適配器 當網絡設備可用時&#xff0c;系統會加載所需的 NDIS 微型端口驅動程序。 隨后&#xff0c;即插即用 (PnP) 管理器向 NDIS 發送即插即用 IRP 來啟動設備。 NDIS 調用微型端口驅動程序的 MiniportInitializeEx 函數來初始化用于網絡 I/O 操作的適配器。 初…

嵩山為什么稱為三水之源

三水指黃河、淮河、濟河&#xff0c;這三條河流環繞在嵩山周邊。 黃河橫亙在嵩山北部&#xff0c;其支流伊洛河從西南方環繞嵩山&#xff0c;然后匯入黃河。濟河&#xff0c;古稱濟水&#xff0c;源自濟源王屋山&#xff0c;自身河道在東晉時代被黃河奪占&#xff0c;從此消失。…

畢設 大數據校園卡數據分析

文章目錄 0 前言1 課題介紹2 數據預處理2.1 數據清洗2.2 數據規約 3 模型建立和分析3.1 不同專業、性別的學生與消費能力的關系3.2 消費時間的特征分析 4 Web系統效果展示5 最后 0 前言 &#x1f525; 這兩年開始畢業設計和畢業答辯的要求和難度不斷提升&#xff0c;傳統的畢設…

職場不是掙錢

職場怎么不是掙錢&#xff1f; 曾經我也一直這么想&#xff0c;只要做好老板安排的事情&#xff0c;自然就可以掙到錢了。 目的應該是沒錯的&#xff0c;是掙錢。 只是做好活就能掙錢&#xff0c;好像想得有些簡單了。 畢竟每個人都在干活&#xff0c;為什么就該自己掙錢呢&a…

【vue2配置】Vue Router

Vue Router官網 1、npm install vue-router4 2、創建模塊&#xff0c;在src目錄小創/views/map/MapIndex.vue模塊和創router/index.js文件 3、在router/index.js配置路由 import Vue from "vue"; import Router from "vue-router"; // 引入模塊 const Ma…

C語言——在頭?件中#if、_STDC_等字?起什么作??

一、問題 通常&#xff0c;?些程序員都不會去研究頭?件中的內容是什么含義&#xff0c;總覺得亂亂的&#xff0c;有很多 #if、_STDC_、#line 等字符&#xff0c;那么這些字符都各代表什么呢&#xff0c;在頭?件中又起到什么作?呢&#xff1f; 二、解答 在頭?件中存在類似…

智慧校園建設的進階之路

智慧校園的建設現已到達了老練的階段&#xff0c;許多學校設備充滿著數字化信息&#xff0c;進出宿舍樓&#xff0c;校園一卡通體系會記載下學生信息&#xff0c;外來人員闖入會報警&#xff0c;翻開電腦就能查到學生是否在宿舍等……學生的學習和日子都充滿了數字化的痕跡。但…

C# WPF入門學習(三)

目錄 核心架構 核心組件和概念 1. XAML&#xff08;eXtensible Application Markup Language&#xff09; 2. 依賴屬性&#xff08;Dependency Properties&#xff09; 3. 路由事件&#xff08;Routed Events&#xff09; 4. 數據綁定 5. 命令&#xff08;Commands&…

itertools內置模塊的過濾妙用

itertools內置模塊的妙用 過濾源迭代器中的元素 Python內置itertools模塊里有一些函數可以過濾源迭代器中的元素。 islice islice可以在不拷貝數據的前提下&#xff0c;按照下標切割源迭代器。可以只給出切割的終點&#xff0c;也可以同時給出起點和終點&#xff0c;還可以…

MongoDB 覆蓋索引查詢:提升性能的完整指南

MongoDB 覆蓋索引查詢是一種優化數據庫查詢性能的技術&#xff0c;它通過創建適當的索引&#xff0c;使查詢可以直接從索引中獲取所需的數據&#xff0c;而無需訪問實際的文檔數據。這種方式可以減少磁盤 I/O 和內存消耗&#xff0c;提高查詢性能。 基本語法 在 MongoDB 中&a…

SQL練習題:2.4

建表 # 學生表 create table t_student (stu_id varchar(10),stu_name varchar(10),stu_age datetime,stu_sex varchar(10) );# 課程表 create table t_t_course (c_id varchar(10),c_name varchar(10),c_teaid varchar(10) );# 教師表 create table t_t_teacher (tea…

光速入門python的OpenCV

前言 歡迎來到我的博客 個人主頁:北嶺敲鍵盤的荒漠貓-CSDN博客 本文整理python的OpenCV模塊的關鍵知識點 爭取用最短的時間入門OpenCV 并且做到筆記功能直接復制使用 OpenCV簡介 不浪費時間的介紹: 就是類似于ps操作圖片。 至于為什么不直接用ps&#xff0c;因為只有程序能…

【找出滿足差值條件的下標 I】python

目錄 暴力題解 優化&#xff1a;滑動窗口維護大小值 暴力題解 class Solution:def findIndices(self, nums: List[int], indexDifference: int, valueDifference: int) -> List[int]:nlen(nums)for i in range(n):for j in range(n-1,-1,-1):if abs(i-j)>indexDiffere…

海康威視NVR通過ehome協議接入視頻監控平臺,視頻瀏覽顯示3011超時錯誤的問題解決,即:The request timeout! 【3011】

目錄 一、問題描述 二、問題分析 2.1 初步分析 2.2 查看日志 2.3 問題驗證 1、查看防火墻 2、查看安全組 3、問題原因 三、問題解決 3.1 防火墻開放相關端口 3.2 安全組增加規則 3.3 測試 1、TCP端口能夠聯通的情況 2、TCP端口不能夠聯通的情況 四、驗證 五、云…

「51媒體」如何與媒體建立良好關系?

傳媒如春雨&#xff0c;潤物細無聲&#xff0c;大家好&#xff0c;我是51媒體網胡老師。 與媒體建立良好關系對于企業或個人來說都是一項重要的公關活動。 了解媒體&#xff1a;研究媒體和記者的興趣&#xff0c;提供相關且有價值的信息。 建立聯系&#xff1a;通過專業的方式…

牛客NC324 下一個更大的數(三)【中等 雙指針 Java/Go/PHP/C++】參考lintcode 52 · 下一個排列

題目 題目鏈接&#xff1a; https://www.nowcoder.com/practice/475da0d4e37a481bacf9a09b5a059199 思路 第一步&#xff1a;獲取數字上每一個數&#xff0c;組成數組arr 第二步&#xff1a;利用“下一個排列” 問題解題方法來繼續作答&#xff0c;步驟&#xff1a;利用lintc…