day04-商家查詢緩存03

来源:https://www.cnblogs.com/liyuelian/archive/2023/04/21/17342010.html
-Advertisement-
Play Games

功能02-商鋪查詢緩存03 3.功能02-商鋪查詢緩存 3.6封裝redis工具類 3.6.1需求說明 基於StringRedisTemplate封裝一個工具列,滿足下列需求: 方法1:將任意Java對象序列化為json,並存儲在string類型的key中,並且可以設置TTL過期時間 方法2:將任意 ...


功能02-商鋪查詢緩存03

3.功能02-商鋪查詢緩存

3.6封裝redis工具類

3.6.1需求說明

基於StringRedisTemplate封裝一個工具列,滿足下列需求:

方法1:將任意Java對象序列化為json,並存儲在string類型的key中,並且可以設置TTL過期時間

方法2:將任意Java對象序列化為json,並存儲在string類型的key中,並且可以設置邏輯過期時間,用戶處理緩存擊穿問題(針對熱點key)

方法3:根據指定的key查詢緩存,並反序列化為指定類型,利用緩存空值的方式解決緩存穿透問題

方法4:根據指定的key查詢緩存,並反序列化為指定類型,需要利用邏輯過期解決緩存擊穿問題(針對熱點key)

3.6.2代碼實現

(1)創建redis工具類,封裝上述方法

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
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.*;

/**
 * @author 李
 * @version 1.0
 * 封裝redis工具類
 */
@Component
@Slf4j
public class CacheClient {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 將任意Java對象序列化為json,並存儲在string類型的key中,並且可以設置TTL過期時間
     *
     * @param key   緩存的key值
     * @param value 緩存的value值
     * @param time  過期時間值
     * @param unit  過期的時間單位
     */
    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue()
                .set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 將任意Java對象序列化為json,並存儲在string類型的key中,
     * 並且可以設置邏輯過期時間,用戶處理緩存擊穿問題(針對熱點key)
     *
     * @param key   緩存的key值
     * @param value 緩存的value值
     * @param time  過期時間值
     * @param unit  過期的時間單位
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        //設置邏輯過期時間
        RedisData redisData = new RedisData();
        redisData.setData(value);
        //邏輯過期時間=當前時間+指定的時間
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * 根據指定的key查詢緩存,並反序列化為指定類型,利用緩存空值的方式解決緩存穿透問題
     *
     * @param keyPrefix  查詢的key值的首碼
     * @param id         查詢的key值的尾碼
     * @param type       要轉換的Class類型
     * @param dbFallback 傳入的函數
     * @param time       過期時間值
     * @param unit       時間單位
     * @param <R>        泛型
     * @param <ID>       泛型
     * @return 返回指定的類型對象
     */
    public <R, ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        //redis查詢緩存
        String json = stringRedisTemplate.opsForValue().get(key);
        //判斷json是否存在
        if (StrUtil.isNotBlank(json)) {
            //存在,轉為java對象並返回
            return JSONUtil.toBean(json, type);
        }
        //判斷是否為"",如果是,說明該key是為瞭解決緩存穿透設置的空值
        if ("".equals(json)) {
            //返回錯誤信息
            return null;
        }
        //不存在,根據id查詢資料庫——使用函數式編程
        R r = dbFallback.apply(id);
        if (r == null) {//說明資料庫中沒有該數據
            //緩存空值,應對緩存穿透
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            //返回錯誤信息
            return null;
        }
        //r存在,則將其寫入redis
        this.set(key, r, time, unit);
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR =
            Executors.newFixedThreadPool(10);

    /**
     * 根據指定的key查詢緩存,並反序列化為指定類型,需要利用邏輯過期解決緩存擊穿問題(針對熱點key)
     * @param keyPrefix  查詢的key值的首碼
     * @param id         查詢的key值的尾碼
     * @param type       要轉換的Class類型
     * @param dbFallback 傳入的函數
     * @param time       過期時間值
     * @param unit       時間單位
     * @param <R>        泛型
     * @param <ID>       泛型
     * @return 返回指定的類型對象
     */
    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
                                            Function<ID, R> dbFallback, Long time,
                                            TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //這裡不再考慮緩存穿透問題,因為key永不過期
        if (StrUtil.isBlank(json)) {
            //如果未命中,說明不是熱點key,直接返回null
            return null;
        }
        //如果命中
        //先把json反序列化為對象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        //判斷是否邏輯過期
        if (expireTime.isAfter(LocalDateTime.now())) {
            //未過期,直接返回信息
            return r;
        }
        //過期,獲取互斥鎖
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        if (isLock) {//成功獲取互斥鎖
            //開啟獨立線程
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    //重建緩存
                    //先查詢資料庫
                    R apply = dbFallback.apply(id);
                    //再存入reids緩存
                    this.setWithLogicalExpire(key, apply, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                //釋放互斥鎖
                unLock(lockKey);
            });
        }
        //如果未獲取互斥鎖,直接返回舊數據
        return r;
    }

    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key) {
        stringRedisTemplate.delete(key);
    }
}

(2)修改ShopServiceImpl,調用封裝好的方法,簡化代碼

package com.hmdp.service.impl;

import ...

/**
 * 服務實現類
 *
 * @author 李
 * @version 1.0
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
        implements IShopService {
    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    private CacheClient cacheClient;

    @Override
    public Result queryById(Long id) {
        //緩存穿透
        //Shop shop =
        //        cacheClient.queryWithPassThrough
        //                (CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

        //緩存擊穿方案(邏輯過期)
        Shop shop = cacheClient.queryWithLogicalExpire
                (CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.MINUTES);

        if (shop == null) {
            return Result.fail("店鋪不存在!");
        }
        return Result.ok(shop);
    }

    @Override
    @Transactional
    public Result update(Shop shop) {
        ...
    }
}

3.7緩存總結


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 隨著技術的發展,ASP.NET Core MVC也推出了好長時間,經過不斷的版本更新迭代,已經越來越完善,本系列文章主要講解ASP.NET Core MVC開發B/S系統過程中所涉及到的相關內容,適用於初學者,在校畢業生,或其他想從事ASP.NET Core MVC 系統開發的人員。 經過前幾篇文章 ...
  • C#中的IDGen是一個C#實現的Twitter Snowflake演算法的ID生成器,可以生成全局唯一的ID,支持高併發場景下的ID生成。在本篇文章中,我們將介紹IDGen的使用方法並提供相關的C#示例代碼。 IDGen的介紹 IDGen是一款開源的分散式唯一ID生成器,支持多種ID生成演算法,並且可 ...
  • 用過ASP.NET Core MVC中IActionFilter攔截器的開發人員,都知道這是一個非常強大的MVC攔截器。最近才發現IActionFilter的OnActionExecuting方法,甚至可以獲取Controller的Action方法參數值。 假如我們在ASP.NET Core MVC ...
  • 平臺 windows 需 求 由於我近期有一個比賽,而我的主機又是x86架構的,人家要求使用arm架構的主機,我這窮屌絲,不可 能去買一臺吧,而且隨著國產系統的推進,採用arm架構的主機也越來越多,作為運維我們該怎麼利用x86 來運行arm架構的主機成為了一個問題 需 要的軟體和程式 以下軟體版本皆 ...
  • ansible分離部署LNMP 環境說明: | 系統 | 主機名 | IP | 服務 | | | | | | | centos8 | ansible | 192.168.111.141 | ansible主控機 | | centos8 | nginx | 192.168.111.142 | ngin ...
  • ​根據2022年的DevOps全球調查報告顯示,主流軟體企業採用或部分採用DevOps且已獲得良好成效的占比已達70%,DevOps儼然成為當下軟體開發研究的重要方向。 測試作為軟體開發的必要過程,是提升軟體可靠性、保證軟體質量的關鍵環節。然而,從過往研究文獻來看,希望通過DevOps提升軟體交付效 ...
  • 在這裡分享項目中我經常使用的一種串口收發方式:阻塞發送 + 接收中斷 +空閑中斷 + 環形隊列 項目代碼地址:https://gitee.com/Mokun_gitee/stm32_hal_study.git 一、簡介 串口發送使用最簡單的阻塞發送方式,一般來說都是接收的數據量比較大,發送數據用此方 ...
  • 我學習的過程中,對於連接池和數據源分得不是很清楚,而且我發現有的人將資料庫等同於數據源,或者將數據源等同於連接池,實際上這些說法並不准確。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...