如何解决Redis缓存穿透

如何解决Redis缓存穿透


本篇将带大家了解如何在不同的业务场景下防范Redis缓存穿透,以查询商品业务场景为例子,分别使用缓存null和布隆过滤器的方法来防范缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样请求到达缓存永远不会命中,并且查询数据库的结果也为null,不会写入缓存,从而使得这些请求都会打到数据库上,在并发量非常高的场景下,大量的请求打到数据库上很有可能会压垮数据库

常规情况下的缓存机制

通常客户端对服务端发出热点数据的查询请求时,会先请求到缓存上,若缓存命中则直接返回数据,若未命中则回去查询数据库,如果在数据库中查到了数据则会返回客户端,并且将数据写入缓存,以便下次查询时,请求可以直接从缓存中获取数据,从而减轻数据库的压力
缓存机制
那么此时就会产生缓存穿透的问题,以一个根据ID查询商品的业务为例:

/*** 根据id查询商品* @param id* @return*/@Overridepublic Result queryById(Long id) {// 1.查询redis缓存String jsonItem = redisTemplate.opsForValue().get("item:" + id);// key// 2.命中直接返回if (StrUtil.isNotBlank(jsonItem)) {Item item = JSONUtil.toBean(jsonItem, Item.class);return Result.ok(item);}// 3.未命中查询数据库Item item = getById(id);// 4.数据库中没有返回错误if (item == null) {return Result.fail("商品不存在!");}// 5.写入redisredisTemplate.opsForValue()  // 设置过期时间30min.set("item:" + id, JSONUtil.toJsonStr(item), 30, TimeUnit.MINUTES);// 6.返回数据return Result.ok(item);}

这个案例严格执行了上面的缓存机制,执行步骤为:

  1. 查询redis缓存
  2. 命中则直接返回数据
  3. 未命中则查询数据库
  4. 数据库中无数据则返回错误
  5. 写入redis缓存
  6. 返回数据

此时若是客户端发来一个完全不存在的id的查询请求时,业务逻辑会在第4步时直接返回错误信息,如果有人恶意大量编造虚假ID来对数据库发动大规模的请求攻击时,我们的服务是我无法处理这种情况的,所以为了数据库的安全性考虑,我们必须要解决缓存穿透问题

常规的解决方案有两种:

  • 缓存空对象:当查询到一个不存在的数据时,我们也将空值写入缓存,使得下次请求可以命中缓存

    • 优点:简单粗暴
    • 缺点:容易造成数据短暂不一致、内存消耗增大
  • 布隆过滤:在请求到达缓存之前先利用布隆过滤算法判断对象是否存在,若不存在则会直接拒绝请求

    • 优点:内存消耗较少
    • 缺点:实现复杂,存在误判

缓存空对象

当缓存未命中并且查询数据库后无数据,此时也将空值写入缓存中,在下次查询时,可以直接命中缓存并返回,但是在命中缓存后,要判断是否命中的是空值,如果是控制则返回错误信息,防止将缓存的空对象当作正常的业务对象
内存消耗问题:如果伪造ID发起大规模请求攻击,那么攻击过后缓存中会有许多垃圾数据,也就是我们假如的空对象数据,大大占用了缓存内存,所以我们可以为空对象的缓存添加一个较短的过期时间TTL

/*** 根据id查询商品* @param id* @return*/@Overridepublic Result queryById(Long id) {// 1.查询redisString jsonItem = redisTemplate.opsForValue().get("item:" + id);// 2.命中直接返回if (StrUtil.isNotBlank(jsonItem)) {Item item = JSONUtil.toBean(jsonItem, Item.class);return Result.ok(item);}// 判断是否命中为空值if (jsonItem != null){// 返回错误return Result.fail("商品不存在");}// 3.未命中查询数据库Item item = getById(id);// 4.数据库中没有返回错误if (item == null) {// 将空值写入缓存redisTemplate.opsForValue().set("item:" + id, "", 2, TimeUnit.MINUTES); // 两分钟过期时间// 返回错误信息return Result.fail("商品不存在");}// 5.写入redisredisTemplate.opsForValue().set("item:" + id, JSONUtil.toJsonStr(item), 30, TimeUnit.MINUTES);// 6.返回数据return Result.ok(item);}

布隆过滤

这是一种空间效率极高的概率型数据结构。它可以用来判断一个元素是否在一个集合中。在缓存系统中,可以将数据库中所有可能存在的数据的键(如商品 ID)放入布隆过滤器中。当一个请求到来时,先通过布隆过滤器进行检查,如果布隆过滤器判断该数据不存在,那么就直接返回,不会再去数据库查询;如果布隆过滤器判断可能存在,再去缓存和数据库中查询。布隆过滤器存在一定的误判率,但可以通过调整参数来降低误判率
布隆过滤
那么布隆过滤器又是怎么知道某个值是否存在呢?
底层原理:底层有一个二进制数组,初始时所有位都被设置为 0,有多个哈希函数,其作用是将输入的元素映射到数组中的位置,一个元素进行多次哈希后,将数据不同位置的值设置为1,此时请求一个元素时,就可以使用该元素多次哈希的值在数组中查询是否所有位置全部为1,若不全部为1则证明该元素一定不存在,从而拒绝请求,但是如果所有位置都为1也不一定证明其一定存在,因为可能存在哈希碰撞的情况
原理
实现

1.引入依赖

在pom.xml文件中添加 Guava 的依赖,用于实现布隆过滤器:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version> 
</dependency>

2.创建布隆过滤器实例

可以创建一个工具类或者在合适的配置类中初始化布隆过滤器,示例如下:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.nio.charset.StandardCharsets;@Configuration
public class BloomFilterConfig {private static final int EXPECTED_INSERTIONS = 10000; // 预期插入的元素数量,预估下数据库中商品ID的数量规模private static final double FPP = 0.01; // 误判率,可根据需求调整@Beanpublic BloomFilter<Long> ItemIdBloomFilter() {return BloomFilter.create(Funnels.longFunnel(StandardCharsets.UTF_8), EXPECTED_INSERTIONS, FPP);}
}

3.初始化布隆过滤器数据

需要在合适的时机(比如项目启动后,从数据库加载完初始数据时等)把数据库中已有的店铺 ID 添加到布隆过滤器中,示例代码可以放在一个启动后执行的方法中(比如实现ApplicationRunner接口等方式)

import com.google.common.hash.BloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;import java.util.List;@Component
public class BloomFilterInitializer implements ApplicationRunner {@Autowiredprivate ItemService itemService; // 假设你的业务层接口是这个名字,根据实际调整@Autowiredprivate BloomFilter<Long> ItemIdBloomFilter;@Overridepublic void run(ApplicationArguments args) throws Exception {List<Long> allItemIds = itemService.getAllItemIds(); // 需在ItemService中定义获取所有商品ID的方法for (Long id : allItemIds) {ItemIdBloomFilter.put(id);}}
}

4.在查询方法中使用布隆过滤器进行判断

修改原来的queryById方法,在查询 Redis 之前先通过布隆过滤器判断商品ID 是否可能存在:

@Autowiredprivate BloomFilter<Long> itemIdBloomFilter;/*** 根据id查询商品** @param id* @return*/@Overridepublic Result queryById(Long id) {// 先通过布隆过滤器判断if (!itemIdBloomFilter.mightContain(id)) {return Result.fail("商品不存在");}// 1.查询redisString jsonItem = redisTemplate.opsForValue().get("item:" + id);// 2.命中直接返回if (StrUtil.isNotBlank(jsonItem)) {Item item = JSONUtil.toBean(jsonItem, Item.class);return Result.ok(item);}// 3.未命中查询数据库Item item = getById(id);// 4.数据库中没有返回错误if (item == null) {return Result.fail("商品不存在");}// 5.写入redisredisTemplate.opsForValue().set("item:" + id, JSONUtil.toJsonStr(item), 30, TimeUnit.MINUTES);// 6.返回数据return Result.ok(item);}

以上两种方法都是被动防范缓存穿透的方法,除此之外还有一些方法可以主动防止缓存穿透:

  • 增强ID的复杂度,增大伪造ID的困难程度
  • 做好数据格式校验,校验ID是否遵循特定规则
  • 做好热点数据限流
  • 加强用户权限校验

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 3.0 + MySQL 8.0 + kkFileView 实现完整文件服务

Spring Boot 3.0 MySQL 8.0 kkFileView 实现完整文件服务 背景&#xff1a;比较常见的需求&#xff0c;做成公共的服务&#xff0c;后期维护比较简单&#xff0c;可扩展多个存储介质&#xff0c;上传逻辑简单&#xff0c;上传后提供一个文件id&#xff0c;后期可直接通过此i…

spring6:3容器:IoC

spring6&#xff1a;3容器&#xff1a;IoC 目录 spring6&#xff1a;3容器&#xff1a;IoC3、容器&#xff1a;IoC3.1、IoC容器3.1.1、控制反转&#xff08;IoC&#xff09;3.1.2、依赖注入3.1.3、IoC容器在Spring的实现 3.2、基于XML管理Bean3.2.1、搭建子模块spring6-ioc-xml…

ROS2创建 base 包用于其他模块的参数配置和头文件依赖

Demo 背景 ROS2项目开发中存在以下需求&#xff1a;有多个包需要读取一些共同的配置项(以txt或者yaml形式存在&#xff09;&#xff0c;且依赖于一些公用的utils工具代码(C)。Solution: 创建一个 base_config 包来“存放” 配置文件和公用的头文件。gitee address: Gitee/CDal…

相机动态/在线标定

图1 图2 基本原理 【原理1】平行线在射影变换后会交于一点。如图所示,A为相机光心,蓝色矩形框为归一化平面,O为平面中心。地面四条黄色直线为平行且等距的车道线。HI交其中两条车道线于H、I, 过G作HI的平行线GM交车道线于M。HI、GM在归一化平面上的投影分别为JK、PN,二者会…

记录下nginx接口代理配置问题

其中api和api1是前面定义的upstream&#xff0c;ip相同只是端口不同。 一开始/api1/直接 像api一样 proxy_pass http://api1这样是不行的&#xff0c;因为会代理到 后端的 /api1/...接口&#xff0c;而后端实际接口地址是 /api/..... 所以必须像上面写法才能将外网的 /api…

【Mac】安装Gradle

1、说明 Gradle 运行依赖 JVM&#xff0c;需要先安装JDK&#xff0c;Gradle 与 JDK的版本对应参见&#xff1a;Java Compatibility IDEA的版本也是有要求Gradle版本的&#xff0c;二者版本对应关系参见&#xff1a;Third-Party Software and Licenses 本次 Gradle 安装版本为…

【JavaEE】多线程(7)

一、JUC的常见类 JUC→java.util.concurrent&#xff0c;放了和多线程相关的组件 1.1 Callable 接口 看以下从计算从1加到1000的代码&#xff1a; public class Demo {public static int sum;public static void main(String[] args) throws InterruptedException {Thread …

Linux-实用操作

文章目录 一. 各类实用小技巧(快捷键)1. ctrl c 强制停止2. ctrl d 退出登出3. history 查看历史命令4. !命令前缀&#xff0c;自动匹配上一个命令5. ctrl r&#xff0c;搜索历史命令6. ctrl a | e&#xff0c;光标移动到命令开始或结束7. ctrl ← | →&#xff0c;左右跳…

李飞飞首个“空间智能”模型发布:一张图,生成一个3D世界 | LeetTalk Daily

“LeetTalk Daily”&#xff0c;每日科技前沿&#xff0c;由LeetTools AI精心筛选&#xff0c;为您带来最新鲜、最具洞察力的科技新闻。 在人工智能技术迅速发展的背景下&#xff0c;李飞飞创立的世界实验室于近期发布了首个“空间智能”模型&#xff0c;这一创新成果引发了3D生…

C语言程序设计P5-3【应用函数进行程序设计 | 第三节】——知识要点:函数的嵌套调用和递归调用

知识要点&#xff1a;函数的嵌套调用和递归调用 视频 目录 一、任务分析 二、必备知识与理论 三、任务实施 一、任务分析 本任务要求用递归法求 n!。 我们知道n!n(n-1)(n-2)……1n(n-1)!递归公式为&#xff1a; 1.上面公式分解为n!n(n-1)!&#xff0c;即将求n!的问题变为…

css部分

前面我们学习了HTML&#xff0c;但是HTML仅仅只是做数据的显示&#xff0c;页面的样式比较简陋&#xff0c;用户体验度不高&#xff0c;所以需要通过CSS来完成对页面的修饰&#xff0c;CSS就是页面的装饰者&#xff0c;给页面化妆&#xff0c;让它更好看。 1 层叠样式表&#…

单片机的中断系统

作者简介 彭煜轩&#xff0c;男&#xff0c;银川科技学院计算机与人工智能学院&#xff0c;2022级计算机与科学技术8班本科生&#xff0c;单片机原理及应用课程第3组。 指导老师&#xff1a;王兴泽 电子邮件&#xff1a;1696409709qq.com 前言 本篇文章是参考《单片机原理…

【技巧】Mac上如何显示键盘和鼠标操作

在制作视频教程时&#xff0c;将键盘和鼠标的操作在屏幕上显示出来&#xff0c;会帮助观众更容易地理解。 推荐Mac上两款开源的小软件。 1. KeyCastr 这款工具从2009年至今一直在更新中。 https://github.com/keycastr/keycastr 安装的话&#xff0c;可以从Github上下载最…

ARM架构-Cache的底层原理

在主存与处理器之间加入一个小容量的存储器保存 CPU 最近一段时间内的历史访问数据&#xff0c;将在 CPU 发起访问的时候优先提供数据快速访问&#xff0c;这个介于 CPU 与主存储器之间的小容量高速存储空间我们称之为 Cache&#xff0c;即高速缓存。Cache 作为处理器与主存之间…

Flutter:商品多规格内容总结,响应式数据,高亮切换显示。

如图所示&#xff1a; 代码为练习时写的项目&#xff0c;写的一般&#xff0c;功能实现了&#xff0c;等以后再来优化。 自己模拟的数据结构 var data {id:1,name:精品小米等多种五谷杂粮精品小等多种五谷杂粮,logo:https://cdn.uviewui.com/uview/swiper/1.jpg,price:100.5…

团队管理中如何做好目标管理

团队管理中的目标管理是确保团队高效运行的核心要素之一。 在目标管理中&#xff0c;清晰的目标设定、合理的资源分配、实时的跟踪与反馈机制是成功的关键。首先&#xff0c;设定SMART目标&#xff08;具体、可衡量、可达成、相关性强、时间限定&#xff09;能够有效聚焦团队的…

Unity在运行状态下,当物体Mesh网格发生变化时,如何让MeshCollider碰撞体也随之实时同步变化?

旧版源代码地址&#xff1a;https://download.csdn.net/download/qq_41603955/90087225?spm1001.2014.3001.5501 旧版效果展示&#xff1a; 新版加上MeshCollider后的效果&#xff1a; 注意&#xff1a;在Unity中&#xff0c;当你动态地更改物体的Mesh时&#xff0c;通常期望…

Blender导入下载好的fbx模型像的骨骼像针戳/像刺猬

为什么我下载下来的骨骼模型和我自己绑定的模型骨骼朝向完全不一样 左边是下载的模型 右边是我自己绑定的模型 左边的模型刚刚感觉都是像针一样往外戳的&#xff0c;像刺猬一样那种。 解决方法勾选自动骨骼坐标系

基于Springboot+Vue的在线答题闯关系统

基于SpringbootVue的在线答题闯关系统 前言&#xff1a;随着在线教育的快速发展&#xff0c;传统的教育模式逐渐向互联网教育模式转型。在线答题系统作为其中的一个重要组成部分&#xff0c;能够帮助用户通过互动式的学习方式提升知识掌握度。本文基于Spring Boot和Vue.js框架&…

矿区新发现,改造明星profinet转profibus协议网关也有未来

profinet转profibusDP协议网关&#xff0c;在矿区的一些老设备上&#xff0c;改造升级一定会遇到profibus无法通讯的情况&#xff0c;选择一个协议模块网关是性价比的首选&#xff0c;下面介绍一下协议网关的一些指标 PROFINET 在 PROFIBUS 一侧为 PROFIBUS DP 从站&#xff0…