Redis缓存穿透雪崩击穿及解决

news2024/12/25 13:00:47

 

封装缓存空对象解决缓存穿透与逻辑过期解决缓存击穿工具类

@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.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.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        //写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }
    //解决缓存穿透的代码
    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        //1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            //3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        //判断命中的是否是空值,不为null 就为“” 因为存入的为“”  解决缓存穿透
        if(json != null){
            return null;
        }
        //4.不存在,根据id查询数据库
        QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", id);
        R r = dbFallback.apply(id);
        //5.不存在,返回错误
        if(r == null){
            //将空值写入redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        //6.存在,写入redis
        this.set(key, r, time, unit);
        //7.返回
        return r;
    }

    //创建指定上线的线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    //逻辑过期解决缓存击穿
    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        //1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(json)) {
            return null;
        }
        //4.命中,先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断缓存是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1未过期,直接返回店铺信息
            return r;
        }
        //5.2已过期,需要缓存重建
        //6.缓存重建
        //6.1获取互斥锁
        boolean isLock = tryLock(LOCK_SHOP_KEY + id);
        //6.2判断是否获取互斥锁成功
        if(isLock){
            //6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() ->{
                try {
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //写入redis
                    this.setWithLogicalExpire(key, r1, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(LOCK_SHOP_KEY + id);
                }
            });
        }
        //6.4失败与成功,都返回过期的商铺信息
        return r;
    }
    //获取锁方法
    private boolean tryLock(String key){
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类
        return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)
    }
    //释放锁方法
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }
}

控制层调用

    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.queryById(id);
    }

服务层调用

Result queryById(Long id);
    @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 = queryWithMutex(id);
        //逻辑过期解决缓存击穿
        //缓存击穿测试时,需先用测试类添加数据到数据库
        Shop shop = cacheClient
                .queryWithLogicalExpire(CACHE_SHOP_KEY, id , Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
        if(shop == null){
            return Result.fail("店铺不存在");
        }
        //返回
        return Result.ok(shop);
    }

常量工具类

public class RedisConstants {
    public static final String LOGIN_CODE_KEY = "login:code:";
    public static final Long LOGIN_CODE_TTL = 2L;
    public static final String LOGIN_USER_KEY = "login:token:";
    public static final Long LOGIN_USER_TTL = 36000L;

    public static final Long CACHE_NULL_TTL = 2L;

    public static final Long CACHE_SHOP_TTL = 30L;
    public static final String CACHE_SHOP_KEY = "cache:shop:";

    public static final String LOCK_SHOP_KEY = "lock:shop:";
    public static final Long LOCK_SHOP_TTL = 10L;
}

数据工具类

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

互斥锁解决缓存击穿

//互斥所解决缓存击穿
    public Shop queryWithMutex(Long id) {
        //1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值,不为null 就为“”  解决缓存穿透
        if(shopJson != null){
            return null;
        }
        Shop shop = null;
        try {
            //4.实现缓存重建
            //4.1 获取互斥锁
            boolean isLock = tryLock(LOCK_SHOP_KEY + id);
            //4.2 判断是否获取成功
            if(!isLock){
                //4.3 失败,则休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //4.4 成功,根据id查询数据库
            QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", id);
            shop = shopMapper.selectOne(queryWrapper);
            //模拟重建的延时
            Thread.sleep(200);
            //5.不存在,返回错误
            if(shop == null){
                //将空值写入redis
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            //6.存在,写入redis
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(LOCK_SHOP_KEY + id);
        }
        //7.返回
        return shop;
    }

    //获取锁方法
    private boolean tryLock(String key){
        Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类
        return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)
    }
    //释放锁方法
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2186078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Linux从小白到高手》理论篇(十一):Linux的系统环境管理

值此国庆佳节&#xff0c;深宅家中&#xff0c;闲来无事&#xff0c;就多写几篇博文。本篇详细深入介绍Linux的系统环境管理。 环境变量 linux系统下&#xff0c;如果你下载并安装了应用程序&#xff0c;很有可能在键入它的名称时出现“command not found”的提示内容。如果每…

震撼!AI造声新标杆,20字生成完美音频

震撼&#xff01;AI造声新标杆&#xff0c;20字生成完美音频 EzAudio是一款革命性的文本到音频生成AI&#x1f3b6;&#xff0c;快速生成高质量音频&#xff0c;告别机械音&#x1f50a;。它能将文字瞬间变成音乐和配音&#xff0c;为创作增添无限可能✨&#xff01;快来体验这…

源2.0全面适配百度PaddleNLP,大模型开发开箱即用

近日&#xff0c;源2.0开源大模型与百度PaddleNLP完成全面适配。用户通过PaddleNLP&#xff0c;可快速调用源2.0预训练大模型&#xff0c;使用源2.0在语义、数学、推理、代码、知识等方面的推理能力&#xff0c;也可以使用特定领域的数据集对源2.0 进行微调&#xff0c;训练出适…

C++11_lambda

lambda表达式 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。比如说&#xff0c;我想在某宝买一件商品&#xff0c;我想买该商品价格最便宜的哪一个&#xff0c;这就需要对价格排升序&#xff1b;我如果想买性价比最高的&…

AAC-Fe³⁺水凝胶,兼具拉伸性与导电性,还有自修复和4D打印能力

大家好&#xff01;今天我们来了解一种用于可拉伸电子产品的创新材料——自修复和4D打印水凝胶——《Self‐Healable and 4D Printable Hydrogel for Stretchable Electronics》发表于《Advanced Science》。在科技发展中&#xff0c;可拉伸电子产品需求大增&#xff0c;但现有…

我尝试了LangGraph Studio的AI Agent功能

构建一个真正“智能”的Agent——一个能够理解语言、做出决策并进行有意义互动的Agent——并不像编写几行代码那么简单。 它需要对AI原理和软件工程有深刻的理解。 此外&#xff0c;传统的软件工具并不适合Agent的开发&#xff0c;无法满足其独特需求。 这也是像LangGraph S…

【数据分享】2001-2023年我国省市县镇四级的逐月平均气温数据(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月平均气温栅格数据&#xff0c;该数据来源于国家青藏高原科学数据中心。为方便大家使用&#xff0c;我们还基于上述平均气温栅格数据将数据处理为Shp和Excel格式的省市县三级逐月平均气温数据&#xff08;可查看之前的文章获悉详情&#…

10.2今日错题解析(软考)

目录 前言面向对象技术——设计模式的应用场景系统开发基础——概要设计与详细设计 前言 这是用来记录我备考软考设计师的错题的&#xff0c;今天知识点为设计模式的应用场景、概要设计与详细设计&#xff0c;大部分错题摘自希赛中的题目&#xff0c;但相关解析是原创&#xf…

银河麒麟V10如何配置外网yum源?

银河麒麟V10如何配置外网yum源&#xff1f; 一、常用的软件源地址二、配置yum源的步骤1. 打开终端2. 进入yum配置文件目录3. 编辑或创建.repo文件4. 配置软件源信息5. 保存并退出6. 更新软件包列表7. 验证软件源 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不…

三色标记算法

三色标记算法 GC---> 标记&#xff08;可达性算法&#xff09;---> 根据不同算法去处理回收 STW&#xff1a;GC时对程序暂停处理下垃圾。不暂停&#xff0c;就会一直制造垃圾&#xff0c;清理不干净。暂停就会阻塞期间请求&#xff0c;影响系统性能 三色标记&#xff…

媒介坊:软文自助发布平台,开启营销新篇章

在数字化时代&#xff0c;企业营销的方式日新月异&#xff0c;软文作为一种高效、低成本的营销手段&#xff0c;越来越受到企业的青睐。然而&#xff0c;如何在众多媒体中精准投放软文&#xff0c;实现品牌的有效传播&#xff0c;成为了众多企业关注的焦点。媒介坊软文自助发布…

闯关训练三:Git 基础知识

任务1: 破冰活动&#xff1a;自我介绍 点击Fork目标项目&#xff0c;创建一个新的Fork 获取仓库链接 在连接好开发机的vscode终端中逐行执行以下代码&#xff1a; git clone https://github.com/KelvinIII/Tutorial.git # 修改为自己frok的仓库 cd Tutorial/ git branch -a g…

在腾讯云上使用docker

第一次使用腾讯云&#xff0c;记录一下过程 因为我平时需求量不大&#xff0c;所以选择的是按需购买 腾讯云服务器购买链接 按照提示一步步往下走&#xff0c;创建实例 如果你不记得密码&#xff0c;那么在下面这幅图中可以重置(选择在线重置&#xff0c;对实例没影响) 因为…

C++——模拟实现vector

1.查看vector的源代码 2.模拟实现迭代器 #pragma oncenamespace jxy {//模板尽量不要分离编译template <class T>class vector{public:typedef T* iterator;//typedef会受到访问限定符的限制typedef const T* const_iterator;//const迭代器是指向的对象不能修改&#xf…

UE5学习笔记23-给角色添加血量,添加收到伤害的功能

零、一些游戏框架知识 1.UE5中包含游戏模式类(Game Mode)、游戏状态类(Game State)、玩家状态类(Player State)、玩家控制器类(Player Controller)、所有的可以被控制的实体或角色类(Pawn)、窗口类(HUD/Widget) Game Mode&#xff1a;存在在服务器上&#xff0c;当我们在客户端…

扣绩效工资,违反劳动法吗?

将工资拆分成绩效工资和岗位工资是很多公司管理员工的一种常见方式。 其中绩效工资跟KPI强挂钩&#xff0c;如果当月没有达到公司绩效标准&#xff0c;那么公司就会扣绩效工资。 那扣绩效工资违反劳动法吗&#xff1f;HR应该如何进行绩效薪酬考核和发放&#xff1f; 扣绩效工…

【网络安全】Cookie与ID未强绑定导致账户接管

未经许可,不得转载。 文章目录 前言正文前言 DigiLocker 是一项在线服务,旨在为公民提供一个安全的数字平台,用于存储和访问重要的文档,如 Aadhaar 卡、PAN 卡和成绩单等。DigiLocker 通过多因素身份验证(MFA)来保护用户账户安全,通常包括 6 位数的安全 PIN 和一次性密…

大论文记录

基础知识回顾 1.强化学习&#xff08;Agent、Environment) 在 RL 中&#xff0c;代理通过不断与环境交互、以试错的方式进行学习&#xff0c;在不确定性下做出顺序决策&#xff0c;并在探索&#xff08;新领域&#xff09;和开发&#xff08;使用从经验中学到的知识&#xff…

五、Java 注释

一、Java 注释 在计算机语言中&#xff0c;注释是计算机语言的一个重要组成部分&#xff0c;用于在源代码中解释代码的作用&#xff0c;可以增强程序的可读性&#xff0c;可维护性。Java 注释是一种在 Java 程序中用于提供代码功能说明的文本。注释不会被编译器包含在最终的可…

数据清洗第3篇章 - 数据异常处理

数据清洗是数据分析过程中至关重要的一步&#xff0c;它确保数据的准确性、一致性和完整性。这不仅有助于提高分析结果的可靠性和有效性&#xff0c;还能为算法建模决策提供高质量的数据基础。在进行数据分析和建模的过程中&#xff0c;大量的时间花在数据准备上&#xff1a;加…