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

Redis解決緩存擊穿問題的兩種方法

 更新時間:2025年03月21日 11:21:38   作者:打死不學Java代碼  
緩存擊穿問題也叫熱點Key問題,就是?個被高并發(fā)訪問并且緩存重建業(yè)務較復雜的key突然失效了,無數(shù)的請求訪問會在瞬間給數(shù)據(jù)庫帶來巨大的沖擊,本文給大家介紹了Redis解決緩存擊穿問題的兩種方法,需要的朋友可以參考下

引言

緩存擊穿:給某一個key設置了過期時間,當key過期的時候,恰好這個時間點對這個key有大量的并發(fā)請求過來,這些并發(fā)的請求可能會瞬間把DB壓垮

解決辦法

互斥鎖(強一致,性能差)

根據(jù)圖片就可以看出,我們的思路就是只能讓一個線程能夠進行訪問Redis,要想實現(xiàn)這個功能,我們也可以使用Redis自帶的setnx

封裝兩個方法,一個寫key來嘗試獲取鎖另一個刪key來釋放鎖

/**
 * 嘗試獲取鎖
 *
 * @param key
 * @return
 */
private boolean tryLock(String key) {
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}
 
/**
 * 釋放鎖
 *
 * @param key
 */
private void unlock(String key) {
    stringRedisTemplate.delete(key);
}

在并行情況下每當其他線程想要獲取鎖,來訪問緩存都要通過將自己的key寫到tryLock()方法里,setIfAbsent()返回false則說明有線程在在更新緩存數(shù)據(jù),鎖未釋放。若返回true則說明當前線程拿到鎖了可以訪問緩存甚至操作緩存。

我們在下面一個熱門的查詢場景中用代碼用代碼來實現(xiàn)互斥鎖解決緩存擊穿,代碼如下:

    /**
     * 解決緩存擊穿的互斥鎖
     * @param id
     * @return
     */
    public Shop queryWithMutex(Long id) {
        String key = CACHE_SHOP_KEY + id;
        //1.從Redis查詢緩存
        String shopJson = stringRedisTemplate.opsForValue().get(key);  //JSON格式
        //2.判斷是否存在
        if (StrUtil.isNotBlank(shopJson)) { //不為空就返回 此工具類API會判斷" "為false
            //存在則直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            //return Result.ok(shop);
            return shop;
        }
        //3.判斷是否為空值 這里過濾 " "的情況,不用擔心會一直觸發(fā)這個條件因為他有TTL
        if (shopJson != null) {
            //返回一個空值
            return null;
        }
        //4.緩存重建 Redis中值為null的情況
        //4.1獲得互斥鎖
        String lockKey = "lock:shop"+id;
        Shop shopById=null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2判斷是否獲取成功
            if (!isLock){
                //4.3失敗,則休眠并重試
                Thread.sleep(50);
               return queryWithMutex(id);
            }
            //4.4成功,根據(jù)id查詢數(shù)據(jù)庫
            shopById = getById(id);
            //5.不存在則返回錯誤
            if (shopById == null) {
                //將空值寫入Redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                //為什么這里要存一個" "這是因為如果后續(xù)DB中有數(shù)據(jù)補充的話還可以去重建緩存
                //return Result.fail("暫無該商鋪信息");
                return null;
            }
            //6.存在,寫入Redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shopById), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.釋放互斥鎖
            unlock(lockKey);
        }
 
        return shopById;
    }

邏輯過期(高可用,性能優(yōu))

方案:用戶查詢某個熱門產品信息,如果緩存未命中(即信息為空),則直接返回空,不去查詢數(shù)據(jù)庫。如果緩存信息命中,則判斷是否邏輯過期,未過期返回緩存信息,過期則重建緩存,嘗試獲得互斥鎖,獲取失敗則直接返回已過期緩存數(shù)據(jù),獲取成功則開啟獨立線程去重構緩存然后直接返回舊的緩存信息,重構完成之后就釋放互斥鎖。

封裝一個方法用來模擬更新邏輯過期時間與緩存的數(shù)據(jù)在測試類里運行起來達到數(shù)據(jù)與熱的效果 

/**
 * 添加邏輯過期時間
 *
 * @param id
 * @param expireTime
 */
public void saveShopRedis(Long id, Long expireTime) {
    //查詢店鋪信息
    Shop shop = getById(id);
    //封裝邏輯過期時間
    RedisData redisData = new RedisData();
    redisData.setData(shop);
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireTime));
    //將封裝過期時間和商鋪數(shù)據(jù)的對象寫入Redis
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

查詢接口:

/**
 * 邏輯過期解決緩存擊穿
 *
 * @param id
 * @return
 */
public Shop queryWithLogicalExpire(Long id) throws InterruptedException {
    String key = CACHE_SHOP_KEY + id;
    Thread.sleep(200);
    //1.從Redis查詢緩存
    String shopJson = stringRedisTemplate.opsForValue().get(key);  //JSON格式
    //2.判斷是否存在
    if (StrUtil.isBlank(shopJson)) {
        //不存在則直接返回
        return null;
    }
    //3.判斷是否為空值
    if (shopJson != null) {
        //返回一個空值
        //return Result.fail("店鋪不存在!");
        return null;
    }
    //4.命中
    //4.1將JSON反序列化為對象
    RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
    Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
    LocalDateTime expireTime = redisData.getExpireTime();
    //4.2判斷是否過期
    if (expireTime.isAfter(LocalDateTime.now())) {
        //5.未過期則返回店鋪信息
        return shop;
    }
    //6.過期則緩存重建
    //6.1獲取互斥鎖
    String LockKey = LOCK_SHOP_KEY + id;
    boolean isLock = tryLock(LockKey);
    //6.2判斷是否成功獲得鎖
    if (isLock) {
        //6.3成功,開啟獨立線程,實現(xiàn)緩存重建
        CACHE_REBUILD_EXECUTOR.submit(() -> {
            try {
                //重建緩存
                this.saveShop2Redis(id, 20L);
 
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //釋放鎖
                unlock(LockKey);
            }
        });
    }
    //6.4返回商鋪信息
    return shop;
}

設計邏輯過期時間

可以用這個方法設置邏輯過期時間

import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
 
public class RedissonExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
 
        RedissonClient redisson = Redisson.create(config);
 
        String key = "exampleKey";
        String value = "exampleValue";
        int timeout = 10; // 過期時間(秒)
 
        // 獲取RBucket對象
        RBucket<String> bucket = redisson.getBucket(key);
 
        // 設置值并指定過期時間
        bucket.set(value, timeout, TimeUnit.SECONDS);
 
        System.out.println("設置成功");
        redisson.shutdown();
    }
}

大家可以看到,邏輯過期鎖就是可以實現(xiàn)并發(fā),所以他的效率更快,性能更好

但是

  • 犧牲了數(shù)據(jù)的實時性,以保證高并發(fā)場景下的服務可用性和數(shù)據(jù)庫的穩(wěn)定性。

  • 在實際應用中,需要確保獲取互斥鎖的操作是原子的,并且鎖具有合適的超時時間,以避免死鎖的發(fā)生。

  • 邏輯過期策略適用于那些對數(shù)據(jù)實時性要求不高,但要求服務高可用性的場景。

到此這篇關于Redis解決緩存擊穿問題的兩種方法的文章就介紹到這了,更多相關Redis解決緩存擊穿內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 淺析Redis底層數(shù)據(jù)結構Dict

    淺析Redis底層數(shù)據(jù)結構Dict

    Redis是一個鍵值型的數(shù)據(jù)庫,我們可以根據(jù)鍵實現(xiàn)快速的增刪改查,而鍵與值的映射關系正是通過Dict來實現(xiàn)的,當然?Dict?也是?Set?Hash?的實現(xiàn)方式,本文就詳細帶大家介紹一下Redis底層數(shù)據(jù)結構?Dict,,需要的朋友可以參考下
    2023-05-05
  • Redis sort 排序命令詳解

    Redis sort 排序命令詳解

    這篇文章主要介紹了Redis sort 排序命令詳解,本文講解了默認排序命令、排序方式命令、BY語法、GET用法示例等內容,需要的朋友可以參考下
    2015-07-07
  • redis底層數(shù)據(jù)結構之skiplist實現(xiàn)示例

    redis底層數(shù)據(jù)結構之skiplist實現(xiàn)示例

    這篇文章主要為大家介紹了redis底層數(shù)據(jù)結構之skiplist實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • 詳解如何清理redis集群的所有數(shù)據(jù)

    詳解如何清理redis集群的所有數(shù)據(jù)

    這篇文章主要介紹了詳解如何清理redis集群的所有數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • 基于redis實現(xiàn)世界杯排行榜功能項目實戰(zhàn)

    基于redis實現(xiàn)世界杯排行榜功能項目實戰(zhàn)

    前段時間,做了一個世界杯競猜積分排行榜。對世界杯64場球賽勝負平進行猜測,猜對+1分,錯誤+0分,一人一場只能猜一次。下面通過本文給大家分享基于redis實現(xiàn)世界杯排行榜功能項目實戰(zhàn),感興趣的朋友一起看看吧
    2018-10-10
  • 利用yum安裝Redis的方法詳解

    利用yum安裝Redis的方法詳解

    Redis是一個開源的使用ANSI C語言編寫、支持網絡、可基于內存亦可持久化的日志型、Key-Value數(shù)據(jù)庫,并提供多種語言的API。從2010年3月15日起,Redis的開發(fā)工作由VMware主持。這篇文章主要介紹的是利用yum安裝Redis的方法,有需要的朋友們可以參考借鑒,下面來一起看看吧
    2016-11-11
  • Redis遠程字典服務器?hash類型示例詳解

    Redis遠程字典服務器?hash類型示例詳解

    這篇文章主要介紹了Redis遠程字典服務器?hash類型示例詳解,本文通過示例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • Redis中管道操作的項目實踐

    Redis中管道操作的項目實踐

    Redis管道操作通過將多個命令一次性發(fā)送到服務器,減少了網絡往返次數(shù),本文就來介紹一下Redis的管道操作,具有一定的參考價值,感興趣的可以了解一下
    2025-02-02
  • Redis高并發(fā)防止秒殺超賣實戰(zhàn)源碼解決方案

    Redis高并發(fā)防止秒殺超賣實戰(zhàn)源碼解決方案

    本文主要介紹了Redis高并發(fā)防止秒殺超賣實戰(zhàn)源碼解決方案,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • 使用Docker部署Redis并配置持久化與密碼保護的詳細步驟

    使用Docker部署Redis并配置持久化與密碼保護的詳細步驟

    本文將詳細介紹如何使用 Docker 部署 Redis,并通過 redis.conf 配置文件實現(xiàn)數(shù)據(jù)持久化和密碼保護,適合在生產環(huán)境中使用,文章通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2025-03-03

最新評論