最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合Redis實現(xiàn)常用功能超詳細過程

 更新時間:2022年08月20日 09:08:34   作者:look-word  
這篇文章主要介紹了SpringBoot整合Redis實現(xiàn)常用功能,登陸功能是每個項目必備的功能吧,但是想設計好,卻是很難,下面介紹兩種登陸功能的解決方式,需要的朋友可以參考下

SpringBoot整合Redis實現(xiàn)常用功能建議大小伙們,在寫業(yè)務的時候,提前畫好流程圖,思路會清晰很多。文末有解決緩存穿透和擊穿的通用工具類。

1 登陸功能

我想,登陸功能是每個項目必備的功能吧,但是想設計好,卻是很難!下面介紹兩種登陸功能的解決方式:

  • 基于Session實現(xiàn)登錄流程
  • 基于Redis實現(xiàn)登錄流程

1.1 基于Session實現(xiàn)登錄流程

功能流程:

發(fā)送驗證碼:

  • 用戶在提交手機號后,會校驗手機號是否合法,如果不合法,則要求用戶重新輸入手機號

  • 如果手機號合法,后臺此時生成對應的驗證碼,同時將驗證碼進行保存,然后再通過短信的方式將驗證碼發(fā)送給用戶

短信驗證碼登錄、注冊:

  • 用戶將驗證碼和手機號進行輸入,后臺從session中拿到當前驗證碼,然后和用戶輸入的驗證碼進行校驗,如果不一致,則無法通過校驗,如果一致,則后臺根據(jù)手機號查詢用戶,
  • 如果用戶不存在,則為用戶創(chuàng)建賬號信息,保存到數(shù)據(jù)庫,無論是否存在,都會將用戶信息保存到session中,方便后續(xù)獲得當前登錄信息

校驗登錄狀態(tài):

  • 用戶在請求時候,會從cookie中攜帶者JsessionId到后臺,后臺通過JsessionId從session中拿到用戶信息,如果沒有session信息,則進行攔截,如果有session信息,則
  • 將用戶信息保存到threadLocal中,并且放行

1.1.1 session共享問題

基于session方式實現(xiàn)登陸功能,最大的缺點就是在多臺tomcat下session無法共享,就會下出現(xiàn)下面問題。

核心思路分析:

每個tomcat中都有一份屬于自己的session,假設用戶第一次訪問第一臺tomcat,并且把自己的信息存放到第一臺服務器的session中,但是第二次這個用戶訪問到了第二臺tomcat,那么在第二臺服務器上,肯定沒有第一臺服務器存放的session,所以此時 整個登錄攔截功能就會出現(xiàn)問題,我們能如何解決這個問題呢?早期的方案是session拷貝,就是說雖然每個tomcat上都有不同的session,但是每當任意一臺服務器的session修改時,都會同步給其他的Tomcat服務器的session,這樣的話,就可以實現(xiàn)session的共享了

但是這種方案具有兩個大問題

1、每臺服務器中都有完整的一份session數(shù)據(jù),服務器壓力過大。

2、session拷貝數(shù)據(jù)時,可能會出現(xiàn)延遲

所以咱們后來采用的方案都是基于redis來完成,我們把session換成redis,redis數(shù)據(jù)本身就是共享的,就可以避免session共享的問題了

1.2 Redis替代Session

1.2.1、設計key的結構

首先我們要思考一下利用redis來存儲數(shù)據(jù),那么到底使用哪種結構呢?由于存入的數(shù)據(jù)比較簡單,我們可以考慮使用String,或者是使用哈希,如下圖,如果使用String,同學們注意他的value,用多占用一點空間,如果使用哈希,則他的value中只會存儲他數(shù)據(jù)本身,如果不是特別在意內存,其實使用String就可以啦。

1.2.2、設計key的具體細節(jié)

所以我們可以使用String結構,就是一個簡單的key,value鍵值對的方式,但是關于key的處理,session他是每個用戶都有自己的session,但是redis的key是共享的,咱們就不能使用code了

在設計這個key的時候,我們之前講過需要滿足兩點:

1、key要具有唯一性2、key要方便攜帶

如果我們采用phone:手機號這個的數(shù)據(jù)來存儲當然是可以的,但是如果把這樣的敏感數(shù)據(jù)存儲到redis中并且從頁面中帶過來畢竟不太合適,所以我們在后臺生成一個隨機串token,然后讓前端帶來這個token就能完成我們的整體邏輯了.

1.2.3、整體訪問流程

當注冊完成后,用戶去登錄會去校驗用戶提交的手機號和驗證碼,是否一致,如果一致,則根據(jù)手機號查詢用戶信息,不存在則新建,最后將用戶數(shù)據(jù)保存到redis,并且生成token作為redis的key,當我們校驗用戶是否登錄時,會去攜帶著token進行訪問,從redis中取出token對應的value,判斷是否存在這個數(shù)據(jù),如果沒有則攔截,如果存在則將其保存到threadLocal中,并且放行。

2 緩存功能

2.1 什么是緩存?

緩存(Cache),就是數(shù)據(jù)交換的緩沖區(qū),俗稱的緩存就是緩沖區(qū)內的數(shù)據(jù),一般從數(shù)據(jù)庫中獲取,存儲于本地代碼(例如:

例1:Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>(); 本地用于高并發(fā)

例2:static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build(); 用于redis等緩存

例3:Static final Map<K,V> map =  new HashMap(); 本地緩存

由于其被Static修飾,所以隨著類的加載而被加載到內存之中,作為本地緩存,由于其又被final修飾,所以其引用(例3:map)和對象(例3:new HashMap())之間的關系是固定的,不能改變,因此不用擔心賦值(=)導致緩存失效;

2.1.1 為什么要使用緩存

一句話:因為速度快,好用

緩存數(shù)據(jù)存儲于代碼中,而代碼運行在內存中,內存的讀寫性能遠高于磁盤,緩存可以大大降低用戶訪問并發(fā)量帶來的服務器讀寫壓力

實際開發(fā)過程中,企業(yè)的數(shù)據(jù)量,少則幾十萬,多則幾千萬,這么大數(shù)據(jù)量,如果沒有緩存來作為"避震器",系統(tǒng)是幾乎撐不住的,所以企業(yè)會大量運用到緩存技術;

但是緩存也會增加代碼復雜度和運營的成本:

2.1.2 如何使用緩存

實際開發(fā)中,會構筑多級緩存來使系統(tǒng)運行速度進一步提升,例如:本地緩存與redis中的緩存并發(fā)使用

瀏覽器緩存:主要是存在于瀏覽器端的緩存

應用層緩存:可以分為tomcat本地緩存,比如之前提到的map,或者是使用redis作為緩存

數(shù)據(jù)庫緩存:在數(shù)據(jù)庫中有一片空間是 buffer pool,增改查數(shù)據(jù)都會先加載到mysql的緩存中

CPU緩存:當代計算機最大的問題是 cpu性能提升了,但內存讀寫速度沒有跟上,所以為了適應當下的情況,增加了cpu的L1,L2,L3級的緩存

2.2.使用緩存

2.2.1 、緩存模型和思路

標準的操作方式就是查詢數(shù)據(jù)庫之前先查詢緩存,如果緩存數(shù)據(jù)存在,則直接從緩存中返回,如果緩存數(shù)據(jù)不存在,再查詢數(shù)據(jù)庫,然后將數(shù)據(jù)存入redis

2.3 緩存更新策略

緩存更新是redis為了節(jié)約內存而設計出來的一個東西,主要是因為內存數(shù)據(jù)寶貴,當我們向redis插入太多數(shù)據(jù),此時就可能會導致緩存中的數(shù)據(jù)過多,所以redis會對部分數(shù)據(jù)進行更新,或者把他叫為淘汰更合適。

內存淘汰:redis自動進行,當redis內存達到咱們設定的max-memery的時候,會自動觸發(fā)淘汰機制,淘汰掉一些不重要的數(shù)據(jù)(可以自己設置策略方式)

超時剔除:當我們給redis設置了過期時間ttl之后,redis會將超時的數(shù)據(jù)進行刪除,方便咱們繼續(xù)使用緩存

主動更新:我們可以手動調用方法把緩存刪掉,通常用于解決緩存和數(shù)據(jù)庫不一致問題

2.3.1 、數(shù)據(jù)庫緩存不一致解決方案:

由于我們的緩存的數(shù)據(jù)源來自于數(shù)據(jù)庫,而數(shù)據(jù)庫的數(shù)據(jù)是會發(fā)生變化的,因此,如果當數(shù)據(jù)庫中數(shù)據(jù)發(fā)生變化,而緩存卻沒有同步,此時就會有一致性問題存在,其后果是:

用戶使用緩存中的過時數(shù)據(jù),就會產生類似多線程數(shù)據(jù)安全問題,從而影響業(yè)務,產品口碑等;怎么解決呢?有如下幾種方案

Cache Aside Pattern 人工編碼方式:緩存調用者在更新完數(shù)據(jù)庫后再去更新緩存,也稱之為雙寫方案(一般采用

Read/Write Through Pattern : 由系統(tǒng)本身完成,數(shù)據(jù)庫與緩存的問題交由系統(tǒng)本身去處理

Write Behind Caching Pattern :調用者只操作緩存,其他線程去異步處理數(shù)據(jù)庫,實現(xiàn)最終一致

2.3.2 、數(shù)據(jù)庫和緩存不一致采用什么方案

綜合考慮使用方案一,但是方案一調用者如何處理呢?這里有幾個問題

操作緩存和數(shù)據(jù)庫時有三個問題需要考慮:

如果采用第一個方案,那么假設我們每次操作數(shù)據(jù)庫后,都操作緩存,但是中間如果沒有人查詢,那么這個更新動作實際上只有最后一次生效,中間的更新動作意義并不大,我們可以把緩存刪除,等待再次查詢時,將緩存中的數(shù)據(jù)加載出來

  • 刪除緩存還是更新緩存?
  • 更新緩存:每次更新數(shù)據(jù)庫都更新緩存,無效寫操作較多
  • 刪除緩存:更新數(shù)據(jù)庫時讓緩存失效,查詢時再更新緩存
  • 如何保證緩存與數(shù)據(jù)庫的操作的同時成功或失???
    • 單體系統(tǒng),將緩存與數(shù)據(jù)庫操作放在一個事務
    • 分布式系統(tǒng),利用TCC等分布式事務方案

應該具體操作緩存還是操作數(shù)據(jù)庫,我們應當是先操作數(shù)據(jù)庫,再刪除緩存,原因在于,如果你選擇第一種方案,在兩個線程并發(fā)來訪問時,假設線程1先來,他先把緩存刪了,此時線程2過來,他查詢緩存數(shù)據(jù)并不存在,此時他寫入緩存,當他寫入緩存后,線程1再執(zhí)行更新動作時,實際上寫入的就是舊的數(shù)據(jù),新的數(shù)據(jù)被舊數(shù)據(jù)覆蓋了。

  • 先操作緩存還是先操作數(shù)據(jù)庫?
  • 先刪除緩存,再操作數(shù)據(jù)庫(存在線程安全問題)
  • 先操作數(shù)據(jù)庫,再刪除緩存

2.4 緩存穿透問題的解決思路

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

常見的解決方案有兩種:

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

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

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

假設布隆過濾器判斷這個數(shù)據(jù)不存在,則直接返回

這種方式優(yōu)點在于節(jié)約內存空間,存在誤判,誤判原因在于:布隆過濾器走的是哈希思想,只要哈希思想,就可能存在哈希沖突

小總結:

緩存穿透產生的原因是什么?

  • 用戶請求的數(shù)據(jù)在緩存中和數(shù)據(jù)庫中都不存在,不斷發(fā)起這樣的請求,給數(shù)據(jù)庫帶來巨大壓力

緩存穿透的解決方案有哪些?

  • 緩存null值
  • 布隆過濾
  • 增強id的復雜度,避免被猜測id規(guī)律
  • 做好數(shù)據(jù)的基礎格式校驗
  • 加強用戶權限校驗
  • 做好熱點參數(shù)的限流

3.工具類

此工具類已經(jīng)對緩存穿透,和緩存擊穿實現(xiàn)了通用功能。

可以對比上敘的流程圖查閱

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;

/**
 * @author : look-word
 * 2022-08-19 17:02
 **/
@Component
public class CacheClient {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 設置邏輯過期時間
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // .封裝邏輯時間
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        redisData.setData(value);
        String redisDataJson = JSONUtil.toJsonStr(redisData);
        // 寫入Redis
        stringRedisTemplate.opsForValue().set(key, redisDataJson);
    }

    /**
     * 解決緩存穿透 對未存在的數(shù)據(jù) 設置為null
     */
    public <R, ID> R queryWithPassThrough
    (String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long cacheTime, TimeUnit cacheUnit) {
        // 緩存key
        String key = keyPrefix + id;
        // 1 查詢緩存中是否命中
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            R r = JSONUtil.toBean(json, type);
            return r;
        }
        // 解決緩存穿透 數(shù)據(jù)庫不存在的數(shù)據(jù) 緩存 也不存在 惡意請求
        if (json != null) {
            return null;
        }

        // 2 查詢數(shù)據(jù)庫 存在 存入緩存 返回給前端
        R r = dbFallback.apply(id);
        if (r == null) {
            // 解決緩存穿透
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        }
        // 2.1 轉換成json 存入緩存中
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), cacheTime, cacheUnit);

        return r;
    }

    // 線程池
    public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 解決緩存擊穿 邏輯過期時間方式
     */
    public <R, ID> R queryWithLogicalExpire
    (String keyPrefix, ID id, Class<R> type, String lockKeyPrefix, Function<ID, R> dbFallback, Long expiredTime, TimeUnit expiredUnit) {
        // 緩存key
        String key = keyPrefix + id;
        // 1 查詢緩存中是否命中
        String redisDataJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(redisDataJson)) {
            return null;
        }
        // 2.命中 查看是否過期,
        //     2.1 未過期 直接返回舊數(shù)據(jù)
        //     2.2 過期 獲取鎖 查詢數(shù)據(jù)寫入Redis設置新的過期時間
        //     2.3 過期 未獲取鎖 返回 舊數(shù)據(jù)
        RedisData redisData = JSONUtil.toBean(redisDataJson, RedisData.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        if (LocalDateTime.now().isBefore(expireTime)) {
            return r;
        }
        String lockKey = lockKeyPrefix + id;
        // 獲取鎖
        boolean isLock = tryLock(lockKey);
        if (isLock) {
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查詢數(shù)據(jù)庫
                    R r1 = dbFallback.apply(id);
                    // 存儲Redis 設置邏輯過期 過期時間
                    setWithLogicalExpire(key, r1, expiredTime, expiredUnit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    // 釋放鎖
                    unlock(lockKey);
                }
            });
        }
        // 未獲取到鎖
        return r;
    }

    /**
     * 獲取鎖
     */
    public boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 100, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    /**
     * 釋放鎖
     */
    public void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

}

到此這篇關于SpringBoot整合Redis實現(xiàn)常用功能超詳細過程的文章就介紹到這了,更多相關SpringBoot整合Redis內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring Cloud Gateway 內存溢出的解決方案

    Spring Cloud Gateway 內存溢出的解決方案

    這篇文章主要介紹了Spring Cloud Gateway 內存溢出的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • maven混淆打包的實現(xiàn)步驟

    maven混淆打包的實現(xiàn)步驟

    本文主要介紹了maven混淆打包的實現(xiàn)步驟,包含了Maven項目混淆、瘦身、打包exe這幾個方面,具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • java中List接口的方法詳解

    java中List接口的方法詳解

    這篇文章主要介紹了java中List接口的方法詳解,List接口是繼承Collection接口,所以Collection集合中有的方法,List集合也繼承過來,本文主要介紹一下list下的方法,需要的朋友可以參考下
    2023-10-10
  • Java基于servlet監(jiān)聽器實現(xiàn)在線人數(shù)監(jiān)控功能的方法

    Java基于servlet監(jiān)聽器實現(xiàn)在線人數(shù)監(jiān)控功能的方法

    這篇文章主要介紹了Java基于servlet監(jiān)聽器實現(xiàn)在線人數(shù)監(jiān)控功能的方法,結合實例形式分析了ServletContextListener監(jiān)聽功能的相關使用步驟與操作技巧,需要的朋友可以參考下
    2018-01-01
  • SpringBoot使用SOFA-Lookout監(jiān)控的方法

    SpringBoot使用SOFA-Lookout監(jiān)控的方法

    本文介紹SpringBoot使用螞蟻金服SOFA-Lookout配合Prometheus進行監(jiān)控,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-03-03
  • Java之String[] args含義及作用

    Java之String[] args含義及作用

    這篇文章主要介紹了Java之String[] args含義及作用,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-08-08
  • 使用Java8?Stream流的skip?+?limit實現(xiàn)批處理的方法

    使用Java8?Stream流的skip?+?limit實現(xiàn)批處理的方法

    Stream 作為 Java 8 的一大亮點,它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念這篇文章主要介紹了使用Java8?Stream流的skip?+?limit實現(xiàn)批處理,需要的朋友可以參考下
    2022-07-07
  • Java的Spring框架下的AOP編程模式示例

    Java的Spring框架下的AOP編程模式示例

    這篇文章主要介紹了Java的Spring框架下的AOP編程模式示例,文中分別講到了基于XML和基于@AspectJ的自定義方式,需要的朋友可以參考下
    2015-12-12
  • Spring MVC+FastJson+hibernate-validator整合的完整實例教程

    Spring MVC+FastJson+hibernate-validator整合的完整實例教程

    這篇文章主要給大家介紹了關于Spring MVC+FastJson+hibernate-validator整合的完整實例教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-04-04
  • SpringBoot中創(chuàng)建bean的7種方式總結

    SpringBoot中創(chuàng)建bean的7種方式總結

    Spring是一款廣泛應用于企業(yè)級應用程序開發(fā)的Java框架,其 IOC 和 DI 特性可以有效地管理應用程序中的對象,提高了應用程序的可維護性和可擴展性,那你知道spring有哪些方式將bean放入容器嘛,今天就給大家總結一下
    2023-07-07

最新評論

恩平市| 平塘县| 阿拉善盟| 保亭| 涡阳县| 博兴县| 隆林| 洞口县| 姚安县| 婺源县| 泰和县| 中阳县| 平乡县| 和硕县| 三明市| 达孜县| 长海县| 东台市| 兴文县| 玉山县| 崇义县| 齐河县| 荔波县| 三门峡市| 高安市| 万山特区| 昌都县| 景洪市| 潞城市| 乐业县| 隆回县| 胶州市| 双鸭山市| 张家口市| 工布江达县| 淮南市| 三亚市| 红河县| 阳泉市| 鄄城县| 伊宁县|