Redis-更新策略,缓存穿透,缓存雪崩,缓存击穿

news2024/11/19 2:31:36

Redis-更新策略,缓存穿透,缓存雪崩,缓存击穿

1.缓存更新 策略

  1. 淘汰策略
  2. 超时剔除
  3. 主动更新
    1. 更新策略:先修改数据库还是先删除缓存 结论:先修改数据库,因为缓存的操作比较快,容易产生数据不一致
    2. 更新缓存还是删除缓存?

2.缓存穿透

客户端请求的数据在缓存和数据库中都不存在,这些请求会访问到数据库

解决方式

  1. 缓存空值:额外内存空间; 短期造成数据不一致
  2. 布隆过滤器,把数据转换成二进制的情况存储,即使在布隆过滤其中存在,实际上也可能不存在,因此有一定的风险
  3. 增加id复杂度,主动预防缓存穿透情况
  4. 增强用户的权限

3.缓存雪崩

是指在同一时间大量的缓存失效或redis服务器宕机,导致大量的请求同时访问数据库。

解决方式:

  1. 设置缓存key随机的TTL
  2. 增加redis服务高可用
  3. 大量的请求限流
  4. 多级缓存,nginx,jvm,浏览器等
  5. 设置热点数据不过期

4.缓存击穿

缓存击穿也叫热点Key问题,一个被高并发访问并且缓存业务重建复杂的key失效了,导致大量的key访问数据库带来冲击。

解决方式:

  1. 互斥锁

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 逻辑过期

    在这里插入图片描述

在这里插入图片描述

  1. 热点数永不过期

  2. 限流熔断

缓存击穿-互斥锁代码实现:
 /** 缓存数据KEY */
    private final String CACHE_SHOP_KEY = "CACHE_SHOP_KEY:";
    /** 缓存互斥锁KEY */
    private final String CACHE_SHOP_LOCK_KEY = "CACHE_SHOP_LOCK_KEY:";

    @Autowired
    private StringRedisTemplate stringRedisTemplate;




/**
     * redis缓存
     * 缓存击穿-互斥锁版本
     * @param bookId
     */
    private BooksVo tryCacheMutex(Long bookId) {
        // RedisKey
        String cacheKey = CACHE_SHOP_KEY + bookId;
        // 1.从Redis查询商铺缓存
        // 获取缓存数据
        String contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否命中
        if (StringUtils.isNotBlank(contentBook)){
            // 3.1缓存命中 直接返回结果
            return JSONUtil.toBean(contentBook, BooksVo.class);
        }
        BooksVo booksVo = null;
        try {
            // 3.2缓存未命中,尝试获取互斥锁
            if (BooleanUtil.isFalse(tryLock(bookId))) {
                // 4.1获取互斥锁失败,尝试重试
                Thread.sleep(50);
                return tryCacheMutex(bookId);
            }
            // 4.2 获取互斥锁成功
            // 4.3 再次检测缓存是存在 doubleCheck
            contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.isNotBlank(contentBook)){
                return JSONUtil.toBean(contentBook, BooksVo.class);
            }
            // 4.4 查询数据库,缓存重建
            booksVo = this.queryById(bookId);
            // 模拟缓存重建延迟
            Thread.sleep(200);
            String jsonBook = JSONUtil.toJsonStr(booksVo);
            stringRedisTemplate.opsForValue().set(cacheKey,jsonBook);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }finally {
            // 5.释放锁
            stringRedisTemplate.delete(CACHE_SHOP_LOCK_KEY + bookId);
        }

        return booksVo;
    }

    /**
     * 获取互斥锁
     */
    public boolean tryLock(Long bookId){
        // redis: set xxx value nx ex 10 添加锁nx是互斥,ex设置过期时间
        Boolean aBoolean = stringRedisTemplate.opsForValue()
            .setIfAbsent(CACHE_SHOP_LOCK_KEY + bookId, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

后期更新逻辑过期的实现方式

========20240209

更新一波工具类完成互斥锁操作

@Slf4j
@Component
@AllArgsConstructor
public class PikerRedisUtils {

    private final StringRedisTemplate stringRedisTemplate;


    /**
     * 存储String值,设置TTL
     */
    private void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 存储逻辑过期时间
     */
    private void setExpiration(String key, Object value, Long time, TimeUnit unit){
        // 封装过期时间
        RedisData redisData = new RedisData();
        redisData.setData(JSONUtil.toJsonStr(value));
        redisData.setExpireSecond(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * <R, ID> 用于指定此方法所处理的泛型类型:
     *   - R:代表方法返回的结果类型,即从缓存或数据库中获取的具体业务对象类型。
     *   - ID:标识要查询的特定资源的唯一标识符类型,通常为某种主键或唯一键。

     * 方法功能:
     *   实现缓存击穿防护机制,结合互斥锁(Mutex)避免大量并发请求直接穿透缓存到达数据库,
     *   造成数据库压力过大。适用于高并发场景下对同一资源(由 `ID` 标识)的频繁访问。

     * 参数说明:
     *   - `key`:用于构造缓存键的基础部分,与 `id` 结合形成完整的缓存键。
     *   - `id`:指定要查询资源的唯一标识符,用于从缓存或数据库中定位具体数据。
     *   - `tClass`:`Class<R>` 类型参数,表示期望从缓存或数据库中获取数据的类类型,用于反序列化 JSON 数据。
     *   - `time`:表示在尝试获取互斥锁时愿意等待的最大时间量。
     *   - `unit`:时间单位,与 `time` 结合确定等待锁的实际时长。
     *   - `lockKey`:互斥锁的键名,在 Redis 中用于实现分布式锁。
     *   - `dbFallback`:`Function<ID, R>` 类型参数,提供一个回调函数,当缓存未命中时,用于查询数据库并返回 `R` 类型数据。

     * 方法逻辑:
     *   1. 构建基于 `key` 和 `id` 的 Redis 缓存键。
     *   2. 尝试从 Redis 中获取缓存数据。
     3. 若缓存命中,则直接反序列化并返回缓存中的数据。
     4. 若缓存未命中:
     a. 尝试获取互斥锁,若获取失败则短暂休眠后递归调用自身重试。
     b. 若成功获取互斥锁,进行双重检查以确认缓存是否在等待锁期间已被其他线程填充。
     c. 若缓存仍为空,则调用 `dbFallback` 函数查询数据库,获取 `R` 类型数据。
     d. 将数据库查询结果转化为 JSON 存储至 Redis,完成缓存重建。
     e. 最后释放互斥锁,确保资源的正确释放。

     * 返回值:
     *   返回与给定 `id` 对应的 `R` 类型数据,该数据来源于缓存(优先)或数据库查询(缓存未命中时)。
     *
     */
    public <R,ID> R getMutex(String key, ID id, Class<R> tClass, Long time,
                             TimeUnit unit, String lockKey, Function<ID, R> dbFallback){
        // RedisKey
        String cacheKey = key + id;
        // 1.从Redis查询商铺缓存
        // 获取缓存数据
        String contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否命中
        if (StringUtils.isNotBlank(contentBook)){
            // 3.1缓存命中 直接返回结果
            return JSONUtil.toBean(contentBook, tClass);
        }
        R r = null;
        try {
            // 3.2缓存未命中,尝试获取互斥锁
            if (BooleanUtil.isFalse(tryLock(lockKey, time ,unit))) {
                // 4.1获取互斥锁失败,尝试重试
                Thread.sleep(50);
                return getMutex(key, id, tClass, time, unit, lockKey, dbFallback);
            }
            // 4.2 获取互斥锁成功
            // 4.3 再次检测缓存是存在 doubleCheck
            contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.isNotBlank(contentBook)){
                return JSONUtil.toBean(contentBook, tClass);
            }
            // 4.4 查询数据库,缓存重建
            r = dbFallback.apply(id);
            // 模拟缓存重建延迟
            // Thread.sleep(200);
            String jsonBook = JSONUtil.toJsonStr(r);
            stringRedisTemplate.opsForValue().set(cacheKey,jsonBook);
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }finally {
            // 5.释放锁
            stringRedisTemplate.delete(lockKey);
        }

        return r;
    }
    
    
    /**
     * 获取互斥锁
     */
    public boolean tryLock(String lockKey,Long time,  TimeUnit unit){
        // redis: set xxx value nx ex 10 添加锁nx是互斥,ex设置过期时间
        Boolean aBoolean = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey, "1", time, unit);
        return BooleanUtil.isTrue(aBoolean);
    }

调用

@Override
    public BooksVo selectById(Long bookId) {
        // 缓存击穿-工具类-互斥锁
        return pikerRedisUtils.getMutex(CACHE_SHOP_KEY,bookId,BooksVo.class, 10L,
            TimeUnit.SECONDS, CACHE_SHOP_LOCK_KEY+bookId,item -> baseMapper.selectVoById(bookId));

        // 缓存击穿-互斥锁
        // return tryCacheMutex(bookId);

        // 缓存击穿-逻辑过期时间
        // return tryCacheMutex2(bookId);

    }

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

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

相关文章

字符串转换为List<String>时候抛出异常:com.alibaba.fastjson2.JSONException: TODO : s

前言&#xff1a; 一个字符串想要能够转换为 List&#xff0c;其本身必须是具备 List 属性的字符串。 问题现象&#xff1a; 项目中需要对第三方接口返回的字符串转换为 List&#xff0c;就想到了使用 fastjson 来处理。 代码如下&#xff1a; Object obj data.get(SignC…

Canal介绍原理及安装

Canal 扩展篇 1.Canal介绍、 链接: https://github.com/alibaba/canal Canal 主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据订阅和消费&#xff0c;工作原理如下&#xff1a; Canal 模拟 MySQL slave 的交互协议&#xff0c;伪装自己为 MySQL slave &am…

启动nginx时报错:signal process started

解决方案&#xff0c;直接使用该命令启动&#xff0c;指向nginx.conf配置文件&#xff1a; nginx -c /www/wdlinux/nginx/conf/nginx.conf 启动成功&#xff1a;

JD抓包 | 安卓app抓包

去年11月份左右搞过一次安卓抓包, 搞了很久试了很多方法, 才弄好. 时隔半年, 安卓抓包依然是令我头疼的问题 这次简单记录一下过程(细节太多我也说不清) JD的有效信息接口通常是以下这样的, 其他的接口并没有返回太多"有用"的信息 https://api.m.jd.com/client.act…

使用 Prometheus 在 KubeSphere 上监控 KubeEdge 边缘节点(Jetson) CPU、GPU 状态

作者&#xff1a;朱亚光&#xff0c;之江实验室工程师&#xff0c;云原生/开源爱好者。 KubeSphere 边缘节点的可观测性 在边缘计算场景下&#xff0c;KubeSphere 基于 KubeEdge 实现应用与工作负载在云端与边缘节点的统一分发与管理&#xff0c;解决在海量边、端设备上完成应…

AI来了,Spring还会远吗?(Spring AI初体验)

目录 一、创建项目二、first demo1、application.properties2、ChatController3、结果 三、个人思考 一、创建项目 官方文档的Getting Started 最低要求&#xff1a;JDK17 阿里云的Server URL&#xff08;https://start.aliyun.com/&#xff09;搜不到Spring AI&#xff0c;…

『VUE』17. Dom与模板引用(详细图文注释)

目录 回顾之前的操作ref 属性借助dom使用原生js总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 回顾之前的操作 之前的这些操作都是我们使用vue为我们渲染的对象,再来操作dom 内容改变{{ 模板语法 }}属性改变 v-bind:添加事…

稀碎从零算法笔记Day49-LeetCode:设计哈希集合

题型&#xff1a;模拟 链接&#xff1a;705. 设计哈希集合 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 不使用任何内建的哈希表库设计一个哈希集合&#xff08;HashSet&#xff09;。 实现 MyHashSet 类&#xff1a; void add(key) 向哈…

LangChain入门:14.LLMChain:最简单的链的使用

摘要 本文将介绍LangChain库中LLMChain工具的使用方法。LLMChain将提示模板、语言模型&#xff08;LLM&#xff09;和输出解析器整合在一起&#xff0c;形成一个连贯的处理链&#xff0c;简化了与语言模型的交互过程。我们将探讨LLMChain的技术特点、应用场景以及它解决的问题…

PostgreSQL入门到实战-第三十弹

PostgreSQL入门到实战 PostgreSQL教程网站官网地址PostgreSQL概述更新计划 PostgreSQL教程网站 https://www.postgresqltutorial.com/ 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://www.postgresql.org/PostgreS…

OpenHarmony实战开发-Actor并发模型对比内存共享并发模型

内存共享并发模型指多线程同时执行复数任务&#xff0c;这些线程依赖同一内存并且都有权限访问&#xff0c;线程访问内存前需要抢占并锁定内存的使用权&#xff0c;没有抢占到内存的线程需要等待其他线程释放使用权再执行。 Actor并发模型每一个线程都是一个独立Actor&#xf…

MySQL limit N offset M 速度慢?来实际体验下

直接开始 有一张表&#xff1a;trade_user&#xff0c;表结构如下&#xff1a; mysql> desc trade_user; ------------------------------------------------------------------ | Field | Type | Null | Key | Default | Extra | -------------…

wpf下RTSP|RTMP播放器两种渲染模式实现

技术背景 在这篇blog之前&#xff0c;我提到了wpf下播放RTMP和RTSP渲染的两种方式&#xff0c;一种是通过控件模式&#xff0c;另外一种是直接原生RTSP、RTMP播放模块&#xff0c;回调rgb&#xff0c;然后在wpf下渲染&#xff0c;本文就两种方式做个说明。 技术实现 以大牛直…

群晖双硬盘实时备份(WebDAV Server+Cloud Sync)

安装和设置 WebDAV Server WebDAV 是一种基于HTTP的协议扩展&#xff0c;它允许用户在远程Web服务器上进行文档的编辑和管理&#xff0c;就如同这些文件存储在本地计算机上一样。使用WebDAV&#xff0c;用户可以创建、移动、复制和修改文件和文件夹。 安装和设置 Cloud Sync…

Spring5深入浅出篇:Spring自定义类型转换器

Spring5深入浅出篇:Spring自定义类型转换器 类型转换器 首先要知道什么叫做类型转换器 ,我们通过配置的属性值是以字符串的形式为什么在查看对象成员变量时已经变成了int,这就是Spring的内置类型转换器帮我们做了自动类型转换. 作⽤&#xff1a;Spring通过类型转换器把配置⽂件…

性能分析与限流策略

性能分析与限流策略 常用HTTP服务压测工具介绍 在项目正式上线之前&#xff0c;我们通常需要通过压测来评估当前系统能够支撑的请求量、排查可能存在的隐藏bug&#xff0c;同时了解了程序的实际处理能力能够帮我们更好地匹配项目的实际需求&#xff0c;节约资源成本 压测相关…

服务器主机关机重启告警

提取时间段内系统操作命名&#xff0c;出现系统重启命令&#xff0c;若要出现及时联系确认 重启命令&#xff1a; reboot / init 6 / shutdown -r now&#xff08;现在重启命令&#xff09; 关机命令&#xff1a; init 0 / shutdown -h now&#xff08;关机&#…

C++ 多态实现机制

考虑下面的 C 程序&#xff1a; class A {void func(){} };class B:public A {void func(){} };int main(void) {cout << sizeof(A) << " " << sizeof(B) << endl;return 0; } 输出结果是&#xff1a;1 1 再考虑下面很相似的程序&#xf…

QtCreater 使用

QtCreater 创建项目 1.刚进入 QtCreater 的界面是这样的一个界面 ① 创建一个新的文件&#xff0c;那么我们就选择左上角的 “文件” ② 点击新建文件&#xff0c;或者也可以直接使用快捷键 CtrlN 此时就会弹出对话框&#xff0c;让我们选择想要创建的文件&#xff1a; Appli…

今天掏心窝子!聊聊35岁了程序员何去何从?

今天的内容不聊技术&#xff0c;聊聊轻松的话题&#xff0c;脑子高速转了好几周&#xff0c;停下来思考一下人生…… 不对&#xff0c;关于35岁的问题好像也不轻松&#xff0c;些许有点沉重&#xff0c;反正不是技术&#xff0c;不用高速转动脑细胞了&#xff0c;哈哈。 兄弟…