缓存穿透的解决办法有哪些?

news2024/11/29 0:29:22

一、概述

缓存穿透是指查询一个不存在的数据,由于缓存和数据库都没有命中,导致每次请求都需要从数据库中读取数据,增加了数据库的负担。解决缓存穿透的方法有以下几种:

  1. 布隆过滤器(Bloom Filter):使用位数组来表示一个集合,并通过哈希函数将元素映射到数组上。在查询数据时,先判断该数据是否存在于布隆过滤器中,如果存在则直接返回结果,否则再从数据库中查询数据。

  2. 缓存空对象:在缓存中存储空对象,当查询一个不存在的数据时,直接返回空对象,而不是默认值或者错误信息。

  3. 设置热点数据永不过期:对于一些热点数据,可以设置永不过期,这样即使缓存未命中,也不会影响数据的一致性。

  4. 使用分布式锁:在查询数据前先使用分布式锁进行加锁,保证只有一个线程能够访问数据库,其他线程需要等待锁释放后才能进行查询。

  5. 使用数据库的缓存机制:一些数据库提供了自己的缓存机制,可以将查询结果缓存到内存中,减少对数据库的访问次数。

二、布隆过滤器

1、原理

布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,

假设布隆过滤器判断这个数据不存在,则直接返回

这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思想,就可能存在哈希冲突

2、优缺点

  • 优点:内存占用较少,没有多余key
  • 缺点:
    • 实现复杂
    • 存在误判可能

3、流程图

在这里插入图片描述

三、缓存空对象

1、缓存空对象思路分析

当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据,此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到缓存了

2、优缺点

  • 优点:实现简单,维护方便
  • 缺点:
    • 额外的内存消耗
    • 可能造成短期的不一致

3、流程图:

在这里插入图片描述

四、设置热点数据永不过期

1、说明

对于缓存穿透问题,设置热点数据永不过期是一种解决方法。热点数据指的是被频繁访问的数据,如果将这些数据的过期时间设置为永久或者相对较长的时间,即使缓存未命中,也不会影响数据的一致性。

2、具体实现方式:

  • 定义一个包含热点数据的 Map 对象,使用 synchronized 关键字保证并发安全。
private static Map<String, Object> hotDataMap = new ConcurrentHashMap<>();
  • 在添加热点数据时,设置过期时间为永久:
hotDataMap.put("key", value);
hotDataMap.put("key", value); // 可以重复添加
  • 在查询数据时,先从缓存中获取热点数据,如果缓存中不存在,则从数据库中查询并加入缓存:
Object value = hotDataMap.get("key");
if (value == null) {
    // 从数据库中查询数据
    value = getValueFromDatabase();
    hotDataMap.put("key", value);
}
  • 将热点数据放入缓存中:
cache.put(key, value);

需要注意的是:

如果设置了热点数据的永不过期,需要定期清理缓存中的无用数据,以避免占用过多内存。

五、使用分布式锁

1 说明

使用分布式锁可以解决缓存穿透问题。缓存穿透是指查询一个不存在的数据,由于缓存和数据库都没有命中,导致每次请求都需要从数据库中读取数据,增加了数据库的负担。而分布式锁可以在多台服务器之间协调对某个资源的操作,保证同一时间只有一个线程可以对该资源进行操作。

2 实现步骤

  1. 引入分布式锁框架,如 Redis 的分布式锁、Zookeeper 等。

  2. 在查询数据前先使用分布式锁进行加锁,保证只有一个线程能够访问数据库,其他线程需要等待锁释放后才能进行查询。

  3. 如果查询结果为空,则释放锁并返回空对象;如果查询结果不为空,则将结果存入缓存中。

  4. 在更新数据时,也需要使用分布式锁进行加锁,保证只有一个线程能够对缓存进行更新。

  5. 在释放锁时,需要确保所有线程都已经完成了对数据的处理。

3 Redis分布式锁的基本流程:

  1. 客户端尝试获取锁,向Redis服务器发送SETNX命令(SET if Not eXists)。

  2. Redis服务器收到SETNX命令,尝试为客户端设置锁,如果该锁不存在,Redis会将锁设置为1,表示客户端获取了锁;否则,Redis返回0,表示客户端未能获取锁。

  3. 客户端收到Redis服务器返回的结果,如果结果为1,则表示客户端已经成功获取了锁,可以执行后续操作;如果结果为0,则表示客户端未能获取锁,需要再次尝试获取或者等待其他客户端释放锁。

  4. 客户端在执行完任务后,需要释放锁,向Redis服务器发送DEL命令,告诉Redis服务器该客户端已经完成任务,锁不再需要。

  5. Redis服务器收到DEL命令,将该客户端的锁删除,其他客户端可以继续尝试获取锁。

需要注意的是,在分布式环境中,需要使用带有超时时间的锁,以防止锁死。在获取锁时,需要设置一个超时时间,如果在指定时间内未能成功获取锁,则需要放弃获取锁。同时,在释放锁时,需要检查该锁是否属于当前客户端,避免误删其他客户端的锁。

在这里插入图片描述

4、缺点

  • 使用分布式锁会增加系统的复杂度和运行成本。
  • 分布式锁也有可能出现死锁等问题,需要进行合理的设计和调试。

六、具体运用

1、从没有使用到使用缓存NULL值的流程变化

在这里插入图片描述

2、未使用缓存NULL时的代码

 @Override
    public Result queryShopById(Long id) {
        //1 从redis获取
        String shopStr = redisTemplate.opsForValue().get(RedisKey.CACHE_SHOP_PRE + id);
        if (MyStrUtil.isNotEmpty(shopStr)) {
            return Result.ok(JSONUtil.toBean(shopStr, Shop.class));
        }

        Shop shop = this.getById(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        redisTemplate.opsForValue().set(RedisKey.CACHE_SHOP_PRE + id, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);

        return Result.ok(shop);
    }

3、使用缓存NULL时的代码

@Override
    public Result queryShopById(Long id) {
        //1 从redis获取
        String shopStr = redisTemplate.opsForValue().get(RedisKey.CACHE_SHOP_PRE + id);
        if (StringUtils.isNotBlank(shopStr)) {
            return Result.ok(JSONUtil.toBean(shopStr, Shop.class));
        }

        //空字符串
        if(shopStr != null){
            return Result.fail("店铺不存在");
        }

        Shop shop = this.getById(id);
        if (shop == null) {
            redisTemplate.opsForValue().set(RedisKey.CACHE_SHOP_PRE + id, "", 1, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        redisTemplate.opsForValue().set(RedisKey.CACHE_SHOP_PRE + id, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);

        return Result.ok(shop);
    }

七、源码下载:

gitee.com/charlinchenlin/koo-erp

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

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

相关文章

iptables防火墙概念

iptables防火墙 一、iptables概述1.netfilter 与 iptables 的关系1&#xff09;netfilter2&#xff09;iptables 2.四表五链1&#xff09;四表2&#xff09;五链3&#xff09;表的匹配优先级4&#xff09;规则链之间的匹配顺序5&#xff09;规则链内的匹配顺序 二、iptables防火…

国外大神用 ChatGPT 成功打造一个「虚拟空间传送」系统!

公众号关注 “GitHubDaily” 设为 “星标”&#xff0c;每天带你逛 GitHub&#xff01; 相信大家小时候躺在床上&#xff0c;都曾设想过这么一个场景&#xff1a; 当你闭上眼睛时&#xff0c;感觉身心十分安宁&#xff0c;物理世界慢慢淡出&#xff0c;身体也随着变得飘逸&…

【mysql】explain执行计划之select_type列

目录 一、说明二、示例2.1 simple&#xff1a;简单表&#xff0c;不使用union或者子查询2.2 primary&#xff1a;主查询&#xff0c;外层的查询2.3 subquery&#xff1a;select、where之后包含了子查询&#xff0c;在select语句中出现的子查询语句&#xff0c;结果不依赖于外部…

5.21下周黄金走势分析及开盘独家交易策略

近期有哪些消息面影响黄金走势&#xff1f;下周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五(5月19日)美市尾盘&#xff0c;现货黄金收报1977.54美元/盎司&#xff0c;大幅上升19.99美元或1.02%&#xff0c;日内最高触及1984.22美元/盎司&#xff0c;最低…

【LeetCode: 10. 正则表达式匹配 | 暴力递归=>记忆化搜索=>动态规划 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【工程化】记录在react工程中eslint、prettier等formatter以及git提交等规范的知识点

文章目录 前言创建eslint安装prettier安装.eslintrc.js完善独立的vscode设置到这一步要重启vscodehuskycommit-lint一切准备就绪&#xff0c;开干&#xff01; 前言 由于使用ACR的方式创建react工程时&#xff0c;并不会像vue一样有每一步的安装提示&#xff0c;需要我们在创建…

用爬虫分析沪深300指数超长走势

我们知道&#xff0c;一个股市里面有非常多的股票&#xff0c;我们如何能够量化整个股市整体的行情呢&#xff0c;答案是通过一些综合性的指数。本文所选用的沪深300就是这类指数中的一个。我们先来看一下百度百科对于沪深300的解释。 由于股票价格起伏无常&#xff0c; 投资者…

跟姥爷深度学习6 卷积网络的数学计算

一、前言 前面简单用TensorFlow的全连接网络做了气温预测然后深入了解了一下全连接网络的数学计算&#xff0c;接着用CNN&#xff08;卷积&#xff09;网络做了手写数字识别&#xff0c;本篇就接着这个节奏来看卷积网络的数学计算。 二、卷积网络回顾 前面我们使用卷积网络时…

setContentHuggingPriority和setContentCompressionResistancePriority的使用

需求&#xff1a; 两个label并排显示&#xff0c;文字内容由服务器返回&#xff0c;label宽度以文字内容自适应&#xff0c;label之间间距大于等于10. 需要考虑以下情况&#xff1a; 当两个label的宽度和 < 屏幕宽度时&#xff0c;各自设置约束&#xff0c;无需处理&#…

【数据结构】Bloom Filter 布隆过滤器

背景 在分布式系统中&#xff0c;比如缓存Redis中&#xff0c;当出现缓存击穿问题&#xff0c;同时访问缓存和数据库都查询不到数据时&#xff0c;对缓存和数据库压力比较大&#xff0c;那么有没有好的数据结构可以快速查询一个数据是否在数据库中&#xff0c;而这个就是大名鼎…

韦东山驱动大全:第四篇 基础-Makefile

1、mount -t nfs -o nolock,vers3 192.168.1.137:/home/book/nfs_rootfs(服务器目录) /mnt(板子目录) 2、gcc -o hello hello.c -v 3、 4、

golang 微服务中的断路器 hystrix

之前说到过微服务容错处理&#xff0c;可以使用 断路器 使用断路器的原因是&#xff1a; 当下游的服务因为过载或故障&#xff0c;无法提供服务&#xff0c;我们需要及时的让上游服务知悉&#xff0c;且暂时 熔断 调用方和提供方的调用链&#xff0c;这是为了避免服务雪崩现象…

二层交换机与三层交换机区别

这两种类型交换机的工作方式有所不同&#xff1a; 二层交换机可以识别数据包中的MAC地址&#xff0c;根据MAC地址进行转发&#xff0c;并将这些MAC地址与对应的端口记录在自己内部的一个地址表中。二层交换机不遵循路由算法。三层交换机转发基于目标 IP 地址&#xff0c;数据包…

143. 重排链表

题目描述&#xff1a; 主要思路&#xff1a; 没有找到什么特殊的捷径&#xff0c;主要就是链表的一些基础操作。 实现了三个函数&#xff0c;一个是找到链表的中心结点&#xff0c;一个是将中心结点之后的链表进行翻转&#xff0c;另一个就是两段链表进行拼接。 class Soluti…

庄懂的TA笔记(十四十六)<特效:火焰 + 水流>

庄懂的TA笔记&#xff08;十四&十六&#xff09;&#xff1c;特效&#xff1a;火焰 水流&#xff1e; 目录 一、作业展示&#xff1a; 二、示范&#xff1a;火: 参考资料&#xff1a; 实现思路&#xff1a; 实践操作&#xff1a; 三、示范&#xff1a;水: 实现思路&am…

Mybatis Plus 使用@TableLogic实现逻辑删除

文章目录 步骤1:修改数据库表添加deleted列步骤2:实体类添加属性步骤3:运行删除方法知识点1&#xff1a;TableLogic 接下来要讲解是删除中比较重要的一个操作&#xff0c;逻辑删除&#xff0c;先来分析下问题: 这是一个员工和其所签的合同表&#xff0c;关系是一个员工可以签多…

如何用 ChatGPT 帮你10分钟读完数据库论文

本周&#xff0c;OpenAI 向所有 ChatGPT Plus 用户开放了两个重要功能&#xff1a; Web Browsing 和 Plugins 它俩都需要用户自己开启&#xff0c;才能使用&#xff0c;如下&#xff1a; 作为对数据库论文的爱好者&#xff0c;我第一款挑选的 Plugin 便是 ChatWithPDF,毕竟真的…

基于脉冲神经网络的物体检测

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 研究的意义在于探索脉冲神经网络在目标检测上的应用&#xff0c;目前主流的脉冲神经网络训练算法有直接BP训练、STDP无监督训练和训练好的ANN的转化&#xff0c;虽然训练算法众多&#xff0c;但是SNN仍然没有一套成熟的训练算…

games101作业3

作业要求 修改函数 rasterize_triangle(const Triangle& t) in rasterizer.cpp: 在此 处实现与作业 2 类似的插值算法&#xff0c;实现法向量、颜色、纹理颜色的插值。 修改函数 get_projection_matrix() in main.cpp: 将你自己在之前的实验中 实现的投影矩阵填到此处&am…

【JUC基础】08. 三大工具类

1、前言 JUC包中包含了三个非常实用的工具类&#xff1a;CountDownLatch&#xff08;倒计数器&#xff09;&#xff0c;CyclicBarrier&#xff08;循环栅栏&#xff09;&#xff0c;Semaphore&#xff08;信号量&#xff09;。 2、倒计数器&#xff1a;CountDownLatch 2.1、…