Redis从理论到实战:用Redis解决缓存穿透、缓存击穿问题(提供解决方案)

news2024/11/18 3:30:50

文章目录

  • 一、缓存穿透
    • 1、什么是缓存穿透
    • 2、解决方案
  • 二、缓存雪崩
  • 三、缓存击穿
    • 1、什么是缓存击穿
    • 2、解决方案
    • 3、互斥锁解决缓存击穿问题
    • 4、逻辑删除解决缓存击穿问题


加油加油,不要过度焦虑(*^▽^*)

一、缓存穿透

1、什么是缓存穿透

  • 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远都不会生效,这些请求都会打到数据库。
  • 当有人恶意频繁地访问在缓存中和数据库中都不存在的数据时,整个系统就可能出现问题。

2、解决方案

  • 方案一:缓存空对象。即我们把在缓存中和数据库中都不存在的数据缓存到Redis中,并设置过期时间;
优点实现简单,维护方便
缺点额外的内存消耗、也可能会造成短期的数据不一致
  • 方案二:布隆过滤。客户端请求的数据会先请求布隆过滤器,如果存在则放行;如果不存在则拒绝客户端的请求。
优点内存占用较少,没有多余key
缺点实现复杂、存在误判的可能

上个图就明白了:

在这里插入图片描述

代码实现:

  • 首先查看是否命中缓存,如果没有命中,则查看数据库判断是否存在商铺,存在则写入缓存,不存在则将空值("")写入redis;如果命中缓存,则查看是否是空值,如果是空值则返回给用户店铺不存在的提示,如果不是空值则返回数据给客户端。
    @Override
    public Result queryShopById(Long id) {
        String key = CACHE_SHOP_KEY + id;
        String shopCache = redisTemplate.opsForValue().get(key);
        //如果在缓存中查询到商户,则返回数据给前端
        if (StrUtil.isNotBlank(shopCache)) {
            Shop shop = JSONUtil.toBean(shopCache, Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否是空值
        if (shopCache != null){
            return Result.fail("店铺不存在");
        }
        //不存在则根据id在数据库中查找
        Shop shop = shopMapper.selectById(id);
        if (shop == null) {
            //将空值写入redis,设置过期时间为2分钟
            redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //店铺存在,写入缓存,过期时间设置为30分钟
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

流程分析图:

在这里插入图片描述


二、缓存雪崩

  • 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力!

解决方案:

  • 给不同的key的过期时间设置随机值
  • 利用redis集群提高服务的可用性

在这里插入图片描述


三、缓存击穿

1、什么是缓存击穿

  • 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

2、解决方案

常见的解决方案有两种:互斥锁和逻辑过期。

  • 互斥锁:当线程1查询缓存未命中时,会获取一个互斥锁,然后查询数据库并重建缓存数据;在此期间,如果线程2查询缓存也未命中,并不会成功获取互斥锁(因为线程1正在使用),线程2会休眠并重试,直到线程1写入缓存释放锁;线程2缓存命中。

  • 逻辑过期:当线程1查询缓存发现逻辑时间已过期时,会成功获取一个互斥锁,并开启一个新线程,这个新线程会进行查询数据库重建缓存数据的操作,写入缓存时重置逻辑过期时间,最后释放锁;线程1此时返回过期数据。如果有线程3在线程1获取互斥锁后查询缓存数据,发现逻辑时间已过期,就会获取互斥锁但失败,最后也返回过期数据,可以说是不争不抢。

解决方案优点缺点
互斥锁没有额外的内存消耗、保证一致性、实现比较简单线程需要等待,性能会受到影响、可能有死锁风险
逻辑过期线程无需等待,性能较好不保证一致性、有额外的内存消耗、实现复杂

上图:

在这里插入图片描述


3、互斥锁解决缓存击穿问题

@Override
    public Result queryShopById(Long id) {
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }
    //封装缓存击穿方法
    public Shop queryWithMutex(Long id) {
        String key = CACHE_SHOP_KEY + id;
        String shopCache = redisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopCache)) {
            Shop shop = JSONUtil.toBean(shopCache, Shop.class);
            return shop;
        }
        if (shopCache != null) {
            return null;
        }
        //实现缓存重键
        //获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            if (!isLock) {
                Thread.sleep(50);
                //没有拿到锁则重试
                return queryWithMutex(id);
            }
            //成功
            shop = shopMapper.selectById(id);
            if (shop == null) {
                redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放互斥锁
            unlock(lockKey);
        }
        return shop;
    }
    //获取锁,并设置过期时间为5秒
    private boolean tryLock(String key) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁
    private void unlock(String key) {
        redisTemplate.delete(key);
    }

流程分析图:

在这里插入图片描述


4、逻辑删除解决缓存击穿问题

    @Override
    public Result queryShopById(Long id) {
        Shop shop = queryWithLogicalExpire(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }
    public Shop queryWithLogicalExpire(Long id) {
        String key = CACHE_SHOP_KEY + id;
        String shopCache = redisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(shopCache)) {
            return null;
        }
        //命中,需要先把JSON反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopCache, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())) {
            //未过期则直接返回店铺信息
            return shop;
        }
        //已过期,需要缓存重建
        //获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        if (isLock) {
            //成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    //重建缓存
                    this.saveShopToRedis(id, CACHE_REBUILD_TTL);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    unlock(lockKey);
                }
            });
        }
        return shop;
    }
    /**
     * @param id            店铺id
     * @param expireSeconds 设置的过期时间
     */
    public void saveShopToRedis(Long id, Long expireSeconds) {
        //查询店铺数据
        Shop shop = shopMapper.selectById(id);
        //封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //写入redis
        redisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }
    //获取锁,并设置过期时间为5秒
    private boolean tryLock(String key) {
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁
    private void unlock(String key) {
        redisTemplate.delete(key);
    }
    //设置10个线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

流程分析图:

在这里插入图片描述


总结完了,有些难度,需要花时间细细琢磨

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

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

相关文章

重组蛋白/细胞因子的实验操作

在我们进行抗体制备、ELISA、药物研究、免疫实验、细胞培养、晶体结构分析等实验时,免不了要和重组蛋白打交道。MCE 重组蛋白产品涵盖超过 2000 种不同功能的重组蛋白,具有批次间一致性,优异的活性以及极低的内毒素水平等特性,可用…

小学生python游戏编程arcade----坦克大战3

小学生python游戏编程arcade----坦克大战3前言整体解绍1、坦克大战3--未完,只是功能初具1.1 文件结构1.2 类1.3 角色类1.4 粒子类1.5 主程序框1.6 main函数1.7 效果图1.8 代码实现源码获取前言 接以上多篇文章解绍arcade游戏编程的基本知识,回归主题&am…

数商云B2B电商系统商品管理功能剖析,助力家用电器企业业务提效

如今,传统家用电器企业的发展空间不断受到电商渠道的积压,由于许多家电企业缺乏数字化的管理工具,导致管理低效,还很容易产生存货积压、供不应求等问题。随着家用电器市场需求疲软、竞争日趋白热化,家用电器企业亟须加…

密码学 数字签名

消息鉴别的缺陷 消息鉴别保证了数据完整性,消息不被第三方侵犯,但是不保证双方之间的欺骗。如果A发送认证消息给B,可能会存在多种争议: B伪造一个不同的消息,声称是A发的 A否认发过这个消息,B无法证明A确实…

爬虫软件是什么意思

爬虫软件的正宗名称是python计算机编程语言,广泛应用于系统管理任务的处理和Web编程。 python软件为什么叫爬虫软件?爬虫通常指的是网络爬虫,就是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。所以Python被很多人称为…

抗疫众志成城网页设计成品 抗击疫情感动人物网页制作模板 大学生抗疫静态HTML网页源码 dreamweaver网页作业致敬逆行者网页设计作品

🎉精彩专栏推荐 💭文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业: 【📚毕设项目精品实战案例 (10…

缓存过期都有哪些策略?

常见以下四种缓存过期策略: 定时过期:每个设置过期时间的key都需要创建⼀个定时器,到过期时间就会立即清除。该策略可以⽴ 即清除过期的数据,对内存很友好;但是会占⽤⼤量的CPU资源去处理过期的数据,从⽽影…

C++11、17、20的内存管理-指针、智能指针和内存池从基础到实战(上)

C11、17、20的内存管理-指针、智能指针和内存池从基础到实战(上)第一章 指针原理和快速入门1、第一个指针程序-详解指针代码2、图示进程的内存空间划分分析代码区_堆栈_内核空间3、各种内存空间-堆_栈_全局地址代码演示4、图解堆栈空间分配对应的指针代码…

中学化学教学参考杂志社中学化学教学参考编辑部2022年第12期目录

教学论坛《中学化学教学参考》投稿:cn7kantougao163.com 探索有效问题的层次化设计和结构化布局 于滨; 1-5 “双减”政策下初中化学作业设计策略与方法探究 王洁; 5-7 中学化学课程思政教学案例设计 兰青;靳素娟;马玲;谢海泉; 8-9 化学教学情境创设…

5G无线技术基础自学系列 | 基础参数及帧结构

素材来源:《5G无线网络规划与优化》 一边学习一边整理内容,并与大家分享,侵权即删,谢谢支持! 附上汇总贴:5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 5G在空中接口的参数定义大多和LTE一致&…

Centos7 安装Seata1.5.1

一、环境说明 IP操作系统程序备注10.0.61.22centos7.9PostgreSQL-14.11已提前部署10.0.61.21centos7.9Nacos-2.1.0已提前部署10.0.61.22centos7.9seata-server-1.5.1本文将要部署 二、部署 1. 下载 wget https://github.com/seata/seata/releases/download/v1.5.1/seata-ser…

【Java八股文总结】之Spring MVC

文章目录Spring MVC1、Spring MVC介绍2、Spring MVC的核心组件3、Spring MVC工作流程4、Spring MVC Restful风格的接口的流程?5、Spring MVC请求参数的种类1. 请求参数(传递json数据)2. 日期类型参数传递6、Spring MVC开发中用到的工具7、Spr…

SRM采购管理系统投标管理模块:阳光招采,助力建筑材料企业智慧采购

在建筑行业企业材料管理的四大业务环节即采购、运输、储备和供应,采购是首要环节,没有采购,就没有材料供应,就没有施工生产的顺利进行,因此采购是决定其他三项业务环节的基础因素。 随着流通环节的不断发展壮大&#…

[go学习笔记.第十六章.TCP编程] 1.基本介绍以及入门案例

1.基本介绍 Golang 的主要设计目标之一就是面向大规模后端服务程序,网络通信这块是服务端程序必不可少也是至关重要的一部分. 网络编程有两种户 (1).TCP Socket 编程,是网络编程的主流。之所以叫TCP Socket 编程,是因为底层是基于 TCP/IP 协议…

镍离子去除专业吸附技术,深度除镍工程段工艺设计

含镍废水具有较大的复杂性,难以利用单一的处理方法进行有效处理,现多采用综合处理技术来实现其达标排放及资源的综合利用。 现有的含镍废水处理技术可分为传统化学法、物理法以及电化学法三类。 传统的化学法包括化学沉淀法以及絮凝法等,是通…

因果推理专题讨论01:因果推理概述

因果推理本质属于统计学范畴,并试图从根源上对基于相关性的统计学进行改革。当年诞生统计学科时就发生过分歧,因果被压下去了。直到最近,基于相关性的统计方法几乎发展到尽头,人工智能进一步发展,目前的统计工具已经难…

HTML5期末大作业:仿商城网站设计—— 绿色特产商城购物Html+Css+javascript的网页制作

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

外卖好评回复模板

好评回复很重要,客户花费时间写好评,如果可以得到商家用心的回复,会更增加客户的好感度,从而将客户转化为店铺忠实粉丝的概率会更大。 前言 在外卖评价上,很多店铺老板都比较重视差评回复,反而会比较忽视好…

【ML】关于什么是概率图模型?

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃 🎁欢迎各位→点赞…

Java程序员该如何进阶?资深阿里P8通过十年经验送你一些经验和建议!

献给迷茫中的你 我相信很多人都有过自己迷茫期,在开始学习之前会迷茫,会不知道自己要学什么,学这些有什么用;学习之后,学的不扎实,得不到认可,觉得自己白学了;真正找到一份还凑合的…