Redis——缓存穿透
文章目录
- 1. 问题介绍
- 1.1 定义
- 1.2 举例
- 2. 解决方案
- 2.1 方案一:空值缓存
- 2.1.1 做法
- 2.1.2 举例
- 2.1.3 示例代码
- 2.1.4 优点
- 2.1.5 缺点
- 2.2 方案二:布隆过滤器
- 2.2.1 思想
- 2.2.2 做法
- 2.2.3 示例代码
- 2.2.4 优点
- 2.2.5 缺点
- 2.3 方案三:限流
- 3. 总结
1. 问题介绍
1.1 定义
缓存穿透:短时间内,大量请求访问不存在的数据,由于这些数据不存在,所以每次处理都需要查询 MySQL 数据库,而且查不到数据也不会将数据缓存到 Reids,MySQL 承受不了高并发,从而宕机。也可以把 缓存穿透 理解成短时间大量查询穿透了 Redis,访问 MySQL,导致 MySQL 宕机。
1.2 举例
在 1s 内,某人恶意攻击服务器,通过某种工具发送了 10000 条 /order/10011
请求,想要查询订单号为 10001
的订单信息,然而这个订单在数据库中并不存在,所以在处理这 10000 条请求时需要在 1s 内访问 10000 次 MySQL 数据库,MySQL 很可能承受不了这么高的并发量,从而宕机。
2. 解决方案
从缓存穿透的定义和举例中可以了解到,解决缓存穿透问题的核心在于 防止短时间内大量请求直接查询 MySQL,所以需要 在应用层阻断查询,方案有以下几种:
2.1 方案一:空值缓存
2.1.1 做法
当查询到数据库中不存在的数据时,可以缓存一个空对象,并设置较短的过期时间。
2.1.2 举例
对于 /order/10011
请求,可以缓存 Order{orderId=null, info=null}
的空数据,键为 order:10001
,过期时间可以取 3s。这样一来,3s 内的其它 /order/10011
请求就不会查询 MySQL 数据库了,从而解决了缓存穿透的问题。
2.1.3 示例代码
public Order get(long orderId) {// 获取缓存对应的键String key = "order:" + orderId;// 如果缓存中有对应的数据,则进一步判断是否为空值缓存Order order = (Order) redisTemplate.opsForValue().get(key);if (order != null) {// 如果为空值缓存,则返回 null,否则返回缓存对象return order.getOrderId() == null ? null : order;}// 如果缓存中没有对应的数据,则从数据库中查询order = orderMapper.getById(orderId);if (order == null) {// 如果数据库中没有对象,则缓存空值对象,过期时间短redisTemplate.opsForValue().set(key, new Order(), 3, TimeUnit.SECONDS);// 返回 nullreturn null;} else {// 如果数据库中有对象,则缓存查询到的对象,过期时间长redisTemplate.opsForValue().set(key, order, 3, TimeUnit.MINUTES);// 返回查询到的对象return order;}
}
2.1.4 优点
- 空值缓存 实现 起来比较 方便。
2.1.5 缺点
- 当保存的空值添加了实际存在的值后,会导致 缓存与数据库的数据不一致。这个问题可以通过在添加新数据时删除新数据对应的缓存来解决。实际上,由于空值缓存的过期时间很短,短时间的数据不一致是可以容忍的。
- 在 Redis 中存储空值也需要 占用一定的内存。实际上,由于空值缓存的过期时间很短,短时间内占用一定内存也是可以容忍的。
2.2 方案二:布隆过滤器
2.2.1 思想
如果启动服务时就记录所有存在的数据,然后在添加(移除)数据时记录数据(移除数据的记录),那么只要一个数据不存在记录中,那么这个数据一定不在数据库中,从而在应用层阻断查询。
初步实现是使用 Set<Long>
来记录存在的数据的主键 id,然而这样占用的内存空间太大了,从而引出了布隆过滤器。它使用了 位数组,将一个值通过多个哈希函数映射,得到多个哈希值,如果这几个哈希值对应的 位 都是 1
,则表示这个值 可能 存在,可以去查询数据库;否则这个值不可能存在,无需查询数据库。
2.2.2 做法
在启动服务时,初始化布隆过滤器,将所有存在数据的主键 id 添加到布隆过滤器中。在添加新的数据时,将新数据的主键 id 添加到布隆过滤器中。在查询数据时,先在布隆过滤器中判断该主键 id 是否可能存在于数据库中,如果不可能存在,则直接返回,否则才查询缓存和数据库。
2.2.3 示例代码
注:本示例代码使用了 Redission 实现的布隆过滤器,Guava 也有相应的布隆过滤器,只不过是本地的,而不是分布式的。
@Service
public class OrderServiceImpl implements InitializingBean {// 布隆过滤器的缓存的键private static final String orderIdBloomFilterKey = "orderIdBloomFilter";private final RedissonClient redissonClient;private final OrderMapper orderMapper;public BloomFilterService(RedissonClient redissonClient, OrderMapper orderMapper) {this.redissonClient = redissonClient;this.orderMapper = orderMapper;}public Order get(long orderId) {// 如果在布隆过滤器中判断该订单的主键 id 不可能存在,则直接返回 nullif (!redissonClient.getBloomFilter(orderIdBloomFilterKey).mightContain(value)) {return null;}// 获取缓存对应的键String key = "order:" + orderId;// 如果缓存中有对应的数据,则返回缓存对象Order order = (Order) redisTemplate.opsForValue().get(key);if (order != null) {return order;}// 如果数据库中没有对象,则返回 nullorder = orderMapper.getById(orderId);if (order == null) {return null;}// 如果数据库中有对象,则缓存查询到的对象,返回查询到的对象redisTemplate.opsForValue().set(key, order, 3, TimeUnit.MINUTES);return order;}@Overridepublic void afterPropertiesSet() throws Exception {RBloomFilter<Long> orderIdBloomFilter = redissonClient.getBloomFilter(orderIdBloomFilterKey);// 初始化布隆过滤器,预计插入 10000000 个元素,误差率为 0.03orderIdBloomFilter.tryInit(10000000, 0.03);// 查询所有订单的主键 id,将其存入布隆过滤器for (long orderId : orderMapper.listAllId()) { orderIdBloomFilter.add(orderId);}}
}
2.2.4 优点
- 由于在判断时只进行了几次哈希操作,所以 时间复杂度很小。
- 由于布隆过滤器底层使用了位数组,所以它 空间复杂度不高,从而能够 处理海量数据。
2.2.5 缺点
- 实现起来很麻烦:由原理就能发现,如果想要自己实现一个布隆过滤器,还是比较难的,而且在使用时还需要在添加值时,将其也添加到布隆过滤器中。
- 不支持删除操作:由于布隆过滤器底层的位数组的每一位被多个值共享,删除一个值可能会影响到其它值的判断,所以布隆过滤器不支持删除操作。
- 存在误判率:由于布隆过滤器使用了哈希,就没有办法避免 哈希碰撞,虽然多个哈希函数可以减少哈希碰撞的概率,但仍可能发生哈希碰撞,所以存在误判的情况。减少哈希碰撞的方法就是给数组扩容,在生产中,一般让误判率小于 5% 即可,既不会占用很多的空间,也不会导致大量请求穿透 Redis。
以下是误判的举例:例如对于 5, 11, 155
这三个值,通过两个(实际上哈希函数不止两个,这里只是用来举例)哈希函数分别得到的哈希值为 1, 9
、3, 7
、1, 7
,那么假如 5, 11
这两个值已存在,155
这个值不存在,如果要查询 155
这个值是否存在,就需要判断位数组中 1, 7
两位是否为 1
,显而易见,结果是存在 155
这个值,这就造成了误判。
2.3 方案三:限流
限流是最直接的解决方案,可以防止 任何情况下 短时间的大量请求导致某些机器承受不住高压而宕机,一般都是留作 保底方案,加在 控制器层。可以自己实现一个拦截器,添加到配置中;或者直接使用 SpringCloudAlibaba 的 Sentinel 组件,使用流量控制等复杂的功能。
3. 总结
Redis 的缓存穿透指的是短时间内大量请求穿透 Redis,直接查询 MySQL 数据库,导致 MySQL 不堪重负,从而宕机。
解决方案主要有两种:
- 空值缓存:在数据库中查询不到数据时,将空对象短暂缓存到 Redis 中,之后短时间内再次查询就无需查询 MySQL 了。实现起来比较方便,但短时间内会占用一定的内存。
- 布隆过滤器:在服务启动时将所有数据的主键 id 存到布隆过滤器中,之后所有查询都先在布隆过滤器中判断是否可能存在,如果不可能存在,则直接返回
null
,否则才需要查询缓存和数据库。性能高,可以处理海量数据,但是实现起来比较麻烦,还存在误判率的缺点。 - 此外,还有一种保底方案——限流,它能解决的问题范围比较广。
相关文章:

Redis——缓存穿透
文章目录 1. 问题介绍1.1 定义1.2 举例 2. 解决方案2.1 方案一:空值缓存2.1.1 做法2.1.2 举例2.1.3 示例代码2.1.4 优点2.1.5 缺点 2.2 方案二:布隆过滤器2.2.1 思想2.2.2 做法2.2.3 示例代码2.2.4 优点2.2.5 缺点 2.3 方案三:限流3. 总结 1.…...

1.gitlab 服务器搭建流程
前提条件: 一、服务器硬件水平 搭建gitlab服务器最低配置要求2核4G,低于这个配置的服务器运行效果很差。 gitlab官网:https://about.gitlab.com/ 下载地址:gitlab/gitlab-ce - Packages packages.gitlab.com 本机ubuntu 二、安装依赖 su…...

McDonald‘s Event-Driven Architecture 麦当劳事件驱动架构
原文链接 1 mcdonalds-technical-blog/ 原文链接 2 mcdonalds-technical-blog/ 麦当劳在异步、事务性和分析性处理用例中使用跨技术栈的事件,包括移动订单进度跟踪和向客户发送营销通信(交易和促销)。 统一事件平台(unified eve…...

GTID详解
概念和组成 1,全局事务表示:global transaction identifiers 2, GTID和事务一一对应,并且全局唯一 3,一个GTID在一个服务器上只执行一次 4,mysql 5.6.5开始支持 组成 GTID server_uuid:transaction_id 如…...

图解HTTP-HTTP状态码
状态码 状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。 类别原因短语1XXInformational(信息状态码)接收的请求正在处理2XXSuccess(成功状态码)请求正常处理完毕4XXRedirection (重定向状态码)需要…...

sh cmake-linux.sh -- --skip-license --prefix = $MY_INSTALL_DIR
本文来自天工AI --------- 命令用于安装CMake的脚本,其中--skip-license参数表示跳过许可协议的显示,--prefix参数指定了CMake的安装目录。$MYINSTALLDIR是一个环境变量,应该在运行命令之前设置为您想要安装CMake的目录。 -------- sh xx…...

MySQL 在window免安装启动
复制my.ini文件 初始化命令:mysqld --initialize --console 执行启动bat:启动mysql.bat 主要命令是:mysqld --standalone 免密码启动mysql: mysqld --defaults-file"D:\xxx\soft\mysql-8.0.40-winx64\my.ini" …...

[JavaScript] 我该怎么去写一个canvas游戏
首先你得知道canvas的基础语法,此处不过多赘述. 一、如何更新视图 canvas里面有个clearRect方法,可以遮住画布中一个矩形部分. 但是你想这样做就难免会遮住一些本不该遮住的东西,因为它是一个矩形,并且你还要计算它的位置和尺寸…...

【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
目录 为什么要结合项目与算法? 1. 蓝桥杯与《苍穹外卖》项目的结合 实例:基于蓝桥杯算法思想的订单配送路径规划 问题描述: 代码实现:使用动态规划解决旅行商问题 代码解析: 为什么这个题目与蓝桥杯相关&#x…...

python报错系列(16)--pyinstaller ????????
系列文章目录 文章目录 系列文章目录前言一、pyinstaller ????????1.报错如下2.安装pyinstaller3.报错如下:4.封装py文件为exe成功5.国内源 总结 前言 一、pyinstaller ??? 1.报错如下 PS D:\Users\gxcaoty\Desktop\性能覆盖率> pyinstaller37.exe…...

Pytorch | 从零构建ResNet对CIFAR10进行分类
Pytorch | 从零构建ResNet对CIFAR10进行分类 CIFAR10数据集ResNet核心思想网络结构创新点优点应用 ResNet结构代码详解结构代码代码详解BasicBlock 类ResNet 类ResNet18、ResNet34、ResNet50、ResNet101、ResNet152函数 训练过程和测试结果代码汇总resnet.pytrain.pytest.py 前…...

Spring Boot 配置Kafka
1 Kafka Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系统。 2 Maven依赖 <dependency><groupId>org.springframework.kafka</groupId><…...

基于单片机的火灾报警器 (论文+源码)
1.系统设计 本系统由火灾检测模块、A/D转换模块、信号处理模块、声光报警模块和灭火装置模块组成。火灾检测模块由温度检测和烟雾检测构成,其温度传感器选用DS18B20,烟雾传感器选用MQ-2烟雾传感器。A/D转换模块选用常用的模数转换芯片ADC0832。声光报警…...

分析excel硕士序列数据提示词——包含对特征的筛选,非0值的过滤
文章目录 1 分析出发点2 围绕出发点的文件分析3 功能模块计算重心相关性计算教学倾向百分比多列相关性计算结果封装证伪——过滤0后的交叉相关系数封装和总控——批量处理特征筛选——筛选提问倾向最大和最小的前五代码总的清洗1 分析出发点 写一个python代码,遍历"D:\Ba…...

MongoDB 更新文档
关于MongoDB更新文档的操作,可以通过多种方法实现。以下是一些常用的方法: updateOne() 方法:用于更新匹配过滤器的单个文档。其语法为 db.collection.updateOne(filter, update, options)。其中,filter 用于查找文档的查询条件&a…...

分布式协同 - 分布式事务_TCC解决方案
文章目录 导图Pre流程图2PC VS 3PC VS TCC2PC(Two-Phase Commit,二阶段提交)3PC(Three-Phase Commit,三阶段提交)TCC(Try-Confirm-Cancel)2PC、3PC与TCC的区别2PC、3PC与TCC的联系 导…...

MFC/C++学习系列之简单记录13
MFC/C学习系列之简单记录13 前言memsetList Control代码注意 总结 前言 今天记录一下memset和List control 的使用吧! memset memset通常在初始化变量或清空内存区域的时候使用,可以对变量设定特定的值。 使用: 头文件: C&#…...

PostgreSQL表达式的类型
PostgreSQL表达式是数据库查询中非常重要的组成部分,它们由一个或多个值、运算符和PostgreSQL函数组合而成,用于计算出一个单一的结果。这些表达式类似于公式,可以用查询语言编写,并用于查询数据库中的特定数据集。 PostgreSQL表…...

速通Python 第四节——函数
一、函数 编程中的函数和数学中的函数有一定的相似之处. 数学上的函数, 比如 y sin x , x 取不同的值, y 就会得到不同的结果. 编程中的函数, 是一段 可以被重复使用的代码片段 代码示例 : 求一段范围的数的和 , 不使用函数 # 1. 求 1 - 100 的和 sum 0 for i in range(1, …...

如何在Windows系统上安装和配置Maven
Maven是一个强大的构建和项目管理工具,广泛应用于Java项目的自动化构建、依赖管理、项目构建生命周期控制等方面。在Windows系统上安装Maven并配置环境变量,是开发者开始使用Maven的第一步。本文将详细介绍如何在Windows系统上安装和配置Maven࿰…...

STM32之GPIO输出与输出
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 一.GPIO输入1.1GPIP简介1.2GPIO基本结构1.3GPIO位结构1.4GPIO的八种模式1.4.1浮空/上拉/下拉输入1.4.2 模拟输入1.4.3 推挽输出\开漏输出 二.GPIO输入2.1.按键介绍2.2传感器模块介绍2.3按键电路 一.G…...

linux定时器操作
目录 1 简单示例2 timer_create方式2.1 SIGEV_SIGNAL信号方式通知2.2 SIGEV_THREAD启动线程方式通知2.3 参数 1 简单示例 #include <stdio.h> #include <stdlib.h> #include <sys/time.h> #include <signal.h> #include <unistd.h>void setup_t…...

重拾设计模式--观察者模式
文章目录 观察者模式(Observer Pattern)概述观察者模式UML图作用:实现对象间的解耦支持一对多的依赖关系易于维护和扩展 观察者模式的结构抽象主题(Subject):具体主题(Concrete Subject…...

Vue.js前端框架教程7:Vue计算属性和moment.js
文章目录 计算属性(Computed Properties)基本用法缓存机制计算属性 vs 方法使用场景计算属性的 setter 和 getter结论Moment.js 进行时间处理1. 安装 Moment.js2. 在 Vue 组件中引入 Moment.js3. 在全局使用 Moment.js4. 使用 Vue 插件的方式引入 Moment.js5. 常用日期格式化…...

【游戏设计原理】22 - 石头剪刀布
一、游戏基础:拳头、掌心、分指 首先,石头剪刀布(又名“Roshambo”)看似简单,实际上可是个“深藏玄机”的零和博弈(听起来很高深,其实就是输赢相抵消的意思)。游戏中有三种手势&…...

3-Gin 渲染 --[Gin 框架入门精讲与实战案例]
在 Gin 框架中,渲染指的是将数据传递给模板,并生成 HTML 或其他格式的响应内容。Gin 支持多种类型的渲染,包括 String HTML、JSON、XML 等。 String 渲染 在 Gin 框架中,String 渲染方法允许你直接返回一个字符串作为 HTTP 响应…...

python小课堂(一)
基础语法 1 常量和表达式2 变量和类型2.1 变量是什么2.2 变量语法 3 变量的类型3.1 动态类型特性 4 注释4.1注释是什么 5 输入输出5.1 print的介绍5.2 input 6 运算符6.1 算术运算符在这里插入图片描述6.2 关系运算符6.3 逻辑运算符6.4赋值运算符 1 常量和表达式 在print()中可…...

GESP202309 二级【小杨的 X 字矩阵】题解(AC)
》》》点我查看「视频」详解》》》 [GESP202309 二级] 小杨的 X 字矩阵 题目描述 小杨想要构造一个 的 X 字矩阵( 为奇数),这个矩阵的两条对角线都是半角加号 ,其余都是半角减号 - 。例如,一个 5 5 5 \times 5 5…...

@PostConstruct注解解释!!!!
PostConstruct 注解修饰的方法是在 Bean 完成初始化后自动调用的。它是 Java EE 和 Spring 中的一种机制,用于在 Bean 被创建并依赖注入完成后,执行一些初始化的操作。 具体触发时机: 依赖注入完成后:首先,Spring 容器…...

laya游戏引擎中打包之后图片模糊
如下图正常运行没问题,打包之后却模糊 纹理类型中的默认类型都是精灵纹理,改为默认值即可。注意:要点击“应用”才可有效。精灵纹理类型会对图片进行渲染处理,而默认值 平面类型不会处理图片。...