欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis緩存實(shí)例超詳細(xì)講解

 更新時間:2022年12月07日 08:32:30   作者:芝麻干  
實(shí)際開發(fā)中緩存處理是必須的,不可能我們每次客戶端去請求一次服務(wù)器,服務(wù)器每次都要去數(shù)據(jù)庫中進(jìn)行查找,為什么要使用緩存?說到底是為了提高系統(tǒng)的運(yùn)行速度

1 前言

1.1 什么是緩存

緩存就是數(shù)據(jù)交換的緩沖區(qū)(稱作Cache [ kæ? ] ),是存貯數(shù)據(jù)的臨時地方,一般讀寫性能較高。

緩存有很多中實(shí)現(xiàn)場景:對于web開發(fā),常見的有如下幾種:

而我們的Redis緩存功能就是屬于在應(yīng)用層緩存 。

1.2 緩存的作用及成本

作用:毫無疑問,就是提高讀寫的效率,有效降低后端服務(wù)器的負(fù)載,有效降低響應(yīng)時間。

成本:任何東西都有兩面性,緩存在帶來高效的讀寫效率的同時,也有著對應(yīng)的從成本。

比如:數(shù)據(jù)一致性成本、代碼維護(hù)成本、運(yùn)維成本等。

1.3 Redis緩存模型

如下圖

原本的模型應(yīng)該是客戶端發(fā)送請求給數(shù)據(jù)庫,數(shù)據(jù)庫返回?cái)?shù)據(jù)給客戶端,而Reids的緩存模型就是在原有的基礎(chǔ)上,在中間加上一層Redis(經(jīng)典的中間件思想~)用戶每次都會先去redis中查找數(shù)據(jù),如果未命中才會去數(shù)據(jù)庫中查找數(shù)據(jù),并寫入Reis當(dāng)中,這么一來,用于下次需要相同的數(shù)據(jù)的時候,就可以在Reis當(dāng)中進(jìn)行獲取,又因?yàn)镽edis的高讀寫效率,實(shí)現(xiàn)了緩存的效果~

2 給商戶信息添加緩存

基于上述的Redis緩存模型,我們可以得出下面的緩存添加邏輯:

代碼實(shí)現(xiàn):(直接看Service層實(shí)現(xiàn))

每次查詢到商品用戶信息后,添加緩存。

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL;
/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查詢商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回給用戶
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //3.不存在,帶著id去數(shù)據(jù)庫查詢是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            //4.不存在,返回錯誤信息
            Result.fail("商品信息不存在!");
        }
        //5.存在,存入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //6.返回商品信息
        return Result.ok(shop);
    }
}

3 緩存更新策略

3.1 更新策略介紹

使用Redis做緩存時,緩存還是要及時更新的,不然就會出現(xiàn)數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的情況,也是我們常說的Redis成本——數(shù)據(jù)一致性成本

緩存大致有以下三種更新策略:

1. 內(nèi)存淘汰策略:這種策略沒有維護(hù)成本,這是利用Redis的內(nèi)存淘汰機(jī)制,當(dāng)內(nèi)存不足的時候自動淘汰,下次請求時再繼續(xù)存入數(shù)據(jù)。

這種策略模型優(yōu)點(diǎn)在于沒有維護(hù)成本,但是內(nèi)存不足這種無法預(yù)定的情況就導(dǎo)致了緩存中會有很多舊的數(shù)據(jù),數(shù)據(jù)一致性差。

2. 超時剔除:這種策略就是比較實(shí)用的,就是給緩存添加TTL存活時間,下次查詢是更新緩存。

這種策略數(shù)據(jù)一致性一般,維護(hù)成本有但是較低,一般用于兜底方案~

3.主動更新策略:就是我們程序員手動的進(jìn)行數(shù)據(jù)庫和緩存之間的更新,但數(shù)據(jù)庫更新時,緩存也進(jìn)行更新。

這種策略數(shù)據(jù)一致性就是最高的(畢竟自己動手,豐衣足食),但同時維護(hù)成本也是最高的。

那么,又該如何選擇緩存更新策略呢?

我的覺得應(yīng)該是根據(jù)業(yè)務(wù)場景不同來選擇不同的更新策略:

當(dāng)數(shù)據(jù)一致性要求低時:l使用內(nèi)存淘汰機(jī)制。例如店鋪類型的查詢緩存。

當(dāng)有高一致性需求:使用主動更新,并以超時剔除作為兜底方案。例如店鋪詳情查詢的緩存

3.2 主動更新策略

上述提到的主動更新策略,無疑是維護(hù)成本最高的,但具體又有哪些維護(hù)方式呢?怎么去做主動更新維護(hù)呢?

如下圖:主動更新主要分為下面三種:

又因?yàn)楹髢煞N實(shí)現(xiàn)方式過于復(fù)雜,所以重點(diǎn)說第一種。

第一種:Cache Aside Pattern,由緩存調(diào)用者進(jìn)行操作,就是在我們數(shù)據(jù)庫進(jìn)行更新時,對緩存也進(jìn)行更新。

這又引出了好幾個問題了~

1. 緩存更新?是更新緩存還是直接刪除緩存?

2. 如何保證數(shù)據(jù)庫更新和緩存更新同時成功或失?。?/p>

3. 操作時應(yīng)該先操作緩存還是先操作數(shù)據(jù)庫?

第一個問題:

我想說,緩存更新如果是數(shù)據(jù)更新的話,每次更新數(shù)據(jù)庫都要對緩存數(shù)據(jù)進(jìn)行更新,有太多無效的讀寫操作,所以操作緩存時,選擇刪除緩存~

第二個問題:

要做到兩個操作一致性,第一想到的就應(yīng)該是事務(wù)。

解決方案:當(dāng)我們是單體系統(tǒng)時,將緩存和數(shù)據(jù)庫操作放在同一個事務(wù)里。

當(dāng)我們是分布式系統(tǒng)時,利用TTC等分布式事務(wù)方案

最后一個問題:先操作數(shù)據(jù)庫還是先操作緩存?

就只有下面兩種情況:

1.先刪除緩存,再操作數(shù)據(jù)庫

2.先操作數(shù)據(jù)庫,再刪除緩存

我們可以兩種情況都來看看~

第一種情況,先刪除緩存的情況,我們想的正常的不會出現(xiàn)問題的操作流程(左邊)和操作會出現(xiàn)問題的流程(右邊)

補(bǔ)充一下:這邊兩個線程最初的數(shù)據(jù)庫和緩存數(shù)據(jù)都假定為10~

出現(xiàn)問題的情況為我們線程1先刪除緩存,線程2未命中緩存,直接查到數(shù)據(jù)庫中數(shù)據(jù)為10并寫入緩存中,而線程1在線程2后寫入緩存后,把數(shù)據(jù)庫更新成了20,這樣最后數(shù)據(jù)庫數(shù)據(jù)為20,而緩存中的數(shù)據(jù)為10,這就導(dǎo)致數(shù)據(jù)不一致了。

2.先操作數(shù)據(jù)庫的情況

正確情況(左邊)錯誤情況(右邊),數(shù)據(jù)庫和緩存最初數(shù)據(jù)也都還是10~

第二種先操作數(shù)據(jù)庫的方式,它出現(xiàn)的錯誤情況主要是,線程1線程查詢了緩存,未命中后去查詢數(shù)據(jù)庫的同時,線程2更新了數(shù)據(jù)庫,并且刪除了緩存,然后線程1才把數(shù)據(jù)庫中查出來的10寫入緩存,導(dǎo)致緩存為10,數(shù)據(jù)庫為20。

終上所述,第一種失敗的情況是比第二種失敗的情況多的,因?yàn)榈谝环N情況出現(xiàn)錯誤的時間是在刪除緩存并更新數(shù)據(jù)庫后,線程2有著充足的時間在這段時間內(nèi)寫入緩存,而對于第二種情況來說,出現(xiàn)問題發(fā)生在查完數(shù)據(jù)庫到寫入緩存這段時間內(nèi),這段時間幾乎是毫秒級別的,線程2在這段時間內(nèi)更新數(shù)據(jù)庫并刪除緩存,顯然幾率是很低的(除了高高高并發(fā)的狀況下),所以我們選擇先操作數(shù)據(jù)庫在操作緩存~

總結(jié):

緩存更新策略的最佳實(shí)踐方案:

1.低一致性需求:使用Redis自帶的內(nèi)存淘汰機(jī)制

2.高一致性需求:主動更新,并以超時剔除作為兜底方案

主動更新時

進(jìn)行讀操作:

  • 緩存命中則直接返回
  • 緩存未命中則查詢數(shù)據(jù)庫,并寫入緩存,設(shè)定超時時間

進(jìn)行寫操作:

  • 先寫數(shù)據(jù)庫,然后再刪除緩存
  • 要確保數(shù)據(jù)庫與緩存操作的原子性

3.3 主動更新策略練習(xí)

修改商品緩存

1. 根據(jù)id查詢店鋪時,如果緩存未命中,則查詢數(shù)據(jù)庫,將數(shù)據(jù)庫結(jié)果寫入緩存,并設(shè)置超時時間

2. 根據(jù)id修改店鋪時,先修改數(shù)據(jù)庫,再刪除緩存

實(shí)現(xiàn)代碼如下(Service層實(shí)現(xiàn)):

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL;
/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查詢商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回給用戶
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //3.不存在,帶著id去數(shù)據(jù)庫查詢是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            //4.不存在,返回錯誤信息
            Result.fail("商品信息不存在!");
        }
        //5.存在,存入redis(并設(shè)置超時時間)
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //6.返回商品信息
        return Result.ok(shop);
    }
    @Override
    @Transactional
    public Result updateShop(Shop shop) {
        //1.先更新數(shù)據(jù)庫
        updateById(shop);
        //2.刪除redis緩存
        String key = CACHE_SHOP_KEY+shop.getId();
        stringRedisTemplate.delete(key);
        return null;
    }
}

這樣就能達(dá)到基本的主動緩存更新啦~

4 緩存穿透及其解決方案

4.1 緩存穿透的概念

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

通俗的說,就是請求的數(shù)據(jù)在數(shù)據(jù)庫和緩存中都沒有,最后請求都到了數(shù)據(jù)庫。這個時候,如果有一個惡意的程序員,通過某種方式不斷請求一個不存在的數(shù)據(jù),這樣就會給數(shù)據(jù)庫帶來巨大的壓力。(就怕用戶懂代碼)

4.2 解決方案及實(shí)現(xiàn)

常見的緩存穿透的解決方案有兩種:

1.就是給redis緩存一個空對象并設(shè)置TTL存活時間

這種方式優(yōu)點(diǎn)在于實(shí)現(xiàn)簡單,維護(hù)方便,但也帶來了額外的內(nèi)存消耗和可能的短期的數(shù)據(jù)不一致。

小知識:這里的數(shù)據(jù)不一致發(fā)生在用戶剛從redis中拿到null值恰好數(shù)據(jù)插入了這個請求需要的值而導(dǎo)致的數(shù)據(jù)庫Redis數(shù)據(jù)不一致。

2. 就是利用布隆過濾

通俗的說,就是中間件~

這種方式 優(yōu)點(diǎn)在于內(nèi)存消耗較小,沒有多余的key,缺點(diǎn)就在于實(shí)現(xiàn)復(fù)雜,而且布隆過濾器有誤判的可能...

代碼實(shí)現(xiàn):這里實(shí)現(xiàn)第一種

就拿上述的寫入商品信息為例:

我們只需要在數(shù)據(jù)庫獲取數(shù)據(jù)時,如果取到空值,不直接返回404,而是將空值也存入redis中,并且在判斷緩存是否命中時,判斷命中的值是不是我們傳入的空值。如果是,直接結(jié)束,不是就返回商鋪信息

package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //1.去redis中查詢商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回給用戶
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //判斷命中的是否為空字符串
        /**
         * (這里重點(diǎn)講一下為什么是不等于:因?yàn)槲覀儷@取到的shopJson為null是,上面的isNotBlank方法會返回false,導(dǎo)致成為了不命中的效果)
         */
        if (shopJson != null){
            return Result.fail("商品信息不存在!");
        }
        //3.不存在,帶著id去數(shù)據(jù)庫查詢是否存在商品
        Shop shop = getById(id);
        if (shop == null){
            // 4.將空值存入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            //5.不存在,返回錯誤信息
            Result.fail("商品信息不存在!");
        }
        //6.存在,存入redis(并設(shè)置超時時間)
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回商品信息
        return Result.ok(shop);
    }
}

總結(jié):

緩存穿透產(chǎn)生的原因是什么?

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

緩存穿透的解決方案有哪些?(3-6點(diǎn)為擴(kuò)展~)

1.緩存null值

2.布隆過濾

3.增強(qiáng)id的復(fù)雜度,避免被猜測id規(guī)律

4.做好數(shù)據(jù)的基礎(chǔ)格式校驗(yàn)

5.加強(qiáng)用戶權(quán)限校驗(yàn)

6.做好熱點(diǎn)參數(shù)的限流

5 緩存雪崩的概念及其解決方案

緩存雪崩是指在同一時段大量的緩存key同時失效或者Redis服務(wù)宕機(jī),導(dǎo)致大量請求到達(dá)數(shù)據(jù)庫,帶來巨大壓力。

就是說,一群設(shè)置了有效期的key同時消失了,或者說redis罷工了,導(dǎo)致所有的或者說大量的請求會給數(shù)據(jù)庫帶來巨大壓力叫做緩存雪崩~

解決方式也比較的簡單~

1. 給不同的key添加隨機(jī)的TTL存活時間(這種就是最簡單的,設(shè)置存貨時間隨機(jī)各不相同)

2. 利用Redis集群(這種針對與Redis出現(xiàn)宕機(jī)的情況)

3. 給緩存業(yè)務(wù)添加降級限流策略(SpringCloud知識點(diǎn))

4. 給業(yè)務(wù)添加多級緩存

6 緩存擊穿及解決方案

6.1 什么是緩存擊穿

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

大概的奔潰流程是這樣子的:

第一個線程,查詢r(jià)edis發(fā)現(xiàn)未命中,然后去數(shù)據(jù)庫查詢并重建緩存,這個時候因?yàn)樵诰彺嬷亟I(yè)務(wù)較為復(fù)雜的情況下,重建時間較久,又因?yàn)楦卟l(fā)的環(huán)境下,在線程1重建緩存的時間內(nèi),會有其他的大量的其他線程進(jìn)來,發(fā)現(xiàn)查找緩存仍未命中,導(dǎo)致繼續(xù)重建,如此死循環(huán)。

6.2 緩存擊穿解決方法

常見的解決方案有兩種:

6.2.1 互斥鎖

互斥鎖的解決方案如下:

就是當(dāng)線程查詢緩存未命中時,嘗試去獲取互斥鎖,然后在重建緩存數(shù)據(jù),在這段時間里,其他線程也會去嘗試獲取互斥鎖,如果失敗就休眠一段時間,并繼續(xù),不斷重試,等到數(shù)據(jù)重建成功,其他線程就可以命中數(shù)據(jù)了。這樣就不會導(dǎo)致緩存擊穿。這個方案數(shù)據(jù)一致性是絕對的,但是相對來說會犧牲性能。

實(shí)現(xiàn)方法:

1.獲取互斥鎖和釋放鎖的實(shí)現(xiàn):

這里我們獲取互斥鎖可以使用redis中string類型中的setnx方法 ,因?yàn)閟etnx方法是在key不存在的情況下才可以創(chuàng)建成功的,所以我們重建緩存時,使用setnx來將鎖的數(shù)據(jù)加入到redis中,并且通過判斷這個鎖的key是否存在,如果存在就是獲取鎖成功,失敗就是獲取失敗,這樣剛好可以實(shí)現(xiàn)互斥鎖的效果。

而釋放鎖就更簡單了,直接刪除我們存入的鎖的key來釋放鎖。

    //獲取鎖
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //釋放鎖方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }

2.實(shí)現(xiàn)代碼:

具體操作流程如下圖:

實(shí)現(xiàn)代碼如下:

package com.hmdp.service.impl;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //互斥鎖緩存擊穿
        Shop shop = queryWithMutex(id);
        if (shop == null){
            Result.fail("商品信息不存在!");
        }
        //8.返回商品信息
        return Result.ok(shop);
    }
    // 緩存穿透解決——互斥鎖
    public Shop queryWithMutex(Long id){
        //1.去redis中查詢商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){
            //2.存在,直接返回給用戶
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判斷命中的是否為空字符串
        if (shopJson != null){
            return null;
        }
        Shop shop = null;
        String lockKey = LOCK_SHOP_KEY+id;
        try {
            //3.緩存重建
            //3.1嘗試獲取鎖
            Boolean isLock = tryLock(lockKey);
            //3.2判斷獲取鎖是否成功,如果休眠重試
            if (!isLock){
                //休眠
                Thread.sleep(50);
                //重試用遞歸
                queryWithMutex(id);
            }
            //3.3如果成功,去數(shù)據(jù)庫查找數(shù)據(jù)
            shop = getById(id);
            if (shop == null){
                // 4.將空值存入redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                //5.不存在,返回錯誤信息
                Result.fail("商品信息不存在!");
            }
            //6.存在,存入redis(并設(shè)置超時時間)
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.釋放鎖
            unlock(lockKey);
        }
        //8.返回商品信息
        return shop;
    }
    //獲取鎖
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //釋放鎖方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }
}

6.2.2 邏輯過期

邏輯過期的處理方法主要為:

給redis緩存字段中添加一個過期時間,然后當(dāng)線程查詢數(shù)據(jù)庫的時候,先判斷是否已經(jīng)過期,如果過期,就獲取獲取互斥鎖,并開啟一個子線程進(jìn)行緩存重建任務(wù),直到子線程完成任務(wù)后,釋放鎖。在這段時間內(nèi),其他線程獲取互斥鎖失敗后,并不是繼續(xù)等待重試,而是直接返回舊數(shù)據(jù)。這個方法雖然性能較好,但也犧牲了數(shù)據(jù)一致性。

實(shí)現(xiàn)方法:

1.獲取互斥鎖和釋放鎖如上

2.給redis數(shù)據(jù)添加一個過期時間(創(chuàng)建一個RedisData類,并封裝數(shù)據(jù))

RedisData類:

package com.hmdp.utils;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
//給商品信息添加一個過期時間字段,并存入redis當(dāng)中
    public void saveShop2Redis(Long id,Long expireSeconds){
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }

然后就是業(yè)務(wù)實(shí)現(xiàn)了:

具體代碼如下:

package com.hmdp.service.impl;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.*;
/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryShopById(Long id) {
        //邏輯過期解決緩存擊穿問題
//        Shop shop = queryWithLogicalExpire(id);
        if (shop == null){
            Result.fail("商品信息不存在!");
        }
        //8.返回商品信息
        return Result.ok(shop);
    }
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    public Shop queryWithLogicalExpire(Long id){
        //1.去redis中查詢商品是否存在
        String key = CACHE_SHOP_KEY+id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判斷是否命中
        //3.未命中
        if (StrUtil.isBlank(shopJson)){
            return null;
        }
        //4.命中,先把redis中的數(shù)據(jù)反序列化成java對象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        //4.1獲取過期時間
        LocalDateTime expireTime = redisData.getExpireTime();
        //4.2獲取商品對象
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        //5.判斷是否過期
        if (expireTime.isAfter(LocalDateTime.now())){
            //未過期,直接返回shop
            return shop;
        }
        //6.過期,重建緩存
        //6.1嘗試獲取鎖,并判斷
        String lockKey = LOCK_SHOP_KEY + id;
        Boolean isLock = tryLock(lockKey);
        if (isLock){
            //5.2 如果成功,開啟一個獨(dú)立的線程,重建緩存
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建緩存
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //釋放鎖
                    unlock(lockKey);
                }
            });
        }
        //6.2返回舊的商品信息
        return shop;
    }
    //獲取鎖
    public Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //釋放鎖方法
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }
    //給商品信息添加一個過期時間字段,并存入redis當(dāng)中
    public void saveShop2Redis(Long id,Long expireSeconds){
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
}

緩存知識結(jié)束。

到此這篇關(guān)于Redis緩存實(shí)例超詳細(xì)講解的文章就介紹到這了,更多相關(guān)Redis緩存策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表使用學(xué)習(xí)

    Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表使用學(xué)習(xí)

    這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)之跳躍表使用學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • 一文詳解如何停止/重啟/啟動Redis服務(wù)

    一文詳解如何停止/重啟/啟動Redis服務(wù)

    Redis是當(dāng)前比較熱門的NOSQL系統(tǒng)之一,它是一個key-value存儲系統(tǒng),這篇文章主要給大家介紹了關(guān)于如何停止/重啟/啟動Redis服務(wù)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • Redis教程(八):事務(wù)詳解

    Redis教程(八):事務(wù)詳解

    這篇文章主要介紹了Redis教程(八):事務(wù)詳解,本文講解了,本文講解了事務(wù)概述、相關(guān)命令列表、命令使用示例、WATCH命令和基于CAS的樂觀鎖等內(nèi)容,需要的朋友可以參考下
    2015-04-04
  • redis中scan命令的基本實(shí)現(xiàn)方法

    redis中scan命令的基本實(shí)現(xiàn)方法

    這篇文章主要給大家介紹了關(guān)于redis中scan命令的基本實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • redis的bigkey掃描腳本深入介紹

    redis的bigkey掃描腳本深入介紹

    這篇文章主要給大家介紹了關(guān)于redis的bigkey掃描腳本的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • 如何用redis?setNX命令來加鎖

    如何用redis?setNX命令來加鎖

    這篇文章主要介紹了如何用redis?setNX命令來加鎖,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • redis執(zhí)行redis命令的方法教程

    redis執(zhí)行redis命令的方法教程

    這篇文章主要給大家介紹了在redis中執(zhí)行redis命令的方法教程,文中詳細(xì)介紹了關(guān)于Redis 命令及在遠(yuǎn)程服務(wù)上執(zhí)行命令的方法,介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2017-06-06
  • Redis如何高效刪除大key

    Redis如何高效刪除大key

    這篇文章主要介紹了Redis如何高效刪除大key問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 如何利用Redis?List實(shí)現(xiàn)Java數(shù)據(jù)庫分頁快速查詢

    如何利用Redis?List實(shí)現(xiàn)Java數(shù)據(jù)庫分頁快速查詢

    這篇文章主要給大家介紹了關(guān)于如何利用Redis?List實(shí)現(xiàn)Java數(shù)據(jù)庫分頁快速查詢的相關(guān)資料,Redis是一個高效的內(nèi)存數(shù)據(jù)庫,它支持包括String、List、Set、SortedSet和Hash等數(shù)據(jù)類型的存儲,需要的朋友可以參考下
    2024-02-02
  • redis常用命令整理

    redis常用命令整理

    在本篇文章里小編給大家整理的是關(guān)于redis常用命令整理相關(guān)內(nèi)容需要的朋友們可以學(xué)習(xí)下。
    2020-03-03

最新評論