Redis——缓存穿透

news/2024/12/28 4:53:01/

文章目录

  • 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 示例代码

java">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 也有相应的布隆过滤器,只不过是本地的,而不是分布式的。

java">@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, 93, 71, 7,那么假如 5, 11 这两个值已存在,155 这个值不存在,如果要查询 155 这个值是否存在,就需要判断位数组中 1, 7 两位是否为 1,显而易见,结果是存在 155 这个值,这就造成了误判。

2.3 方案三:限流

限流是最直接的解决方案,可以防止 任何情况下 短时间的大量请求导致某些机器承受不住高压而宕机,一般都是留作 保底方案,加在 控制器层。可以自己实现一个拦截器,添加到配置中;或者直接使用 SpringCloudAlibaba 的 Sentinel 组件,使用流量控制等复杂的功能。

3. 总结

Redis 的缓存穿透指的是短时间内大量请求穿透 Redis,直接查询 MySQL 数据库,导致 MySQL 不堪重负,从而宕机。

解决方案主要有两种:

  • 空值缓存:在数据库中查询不到数据时,将空对象短暂缓存到 Redis 中,之后短时间内再次查询就无需查询 MySQL 了。实现起来比较方便,但短时间内会占用一定的内存。
  • 布隆过滤器:在服务启动时将所有数据的主键 id 存到布隆过滤器中,之后所有查询都先在布隆过滤器中判断是否可能存在,如果不可能存在,则直接返回 null,否则才需要查询缓存和数据库。性能高,可以处理海量数据,但是实现起来比较麻烦,还存在误判率的缺点。
  • 此外,还有一种保底方案——限流,它能解决的问题范围比较广。

http://www.ppmy.cn/news/1558696.html

相关文章

【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考

文章日期&#xff1a;2024.12.24 使用工具&#xff1a;Python&#xff0c;Node.js 逆向类型&#xff1a;webpack类型 本章知识&#xff1a;sign模拟生成&#xff0c;密文的解密(webpack)&#xff0c;全程扣代码&#xff0c;仅供学习参考 文章难度&#xff1a;低等&#xff08;没…

39.1 用最近1天的内存平均使用率等出业务资源利用率报表

本节重点介绍 : 纵向聚合VS横向聚合用最近1天的内存平均使用率等出业务资源利用率报表 为了降低成本合理配置资源 纵向聚合VS横向聚合 普通的聚合函数是纵向聚合 普通的聚合函数是纵向聚合&#xff0c;也就是多个series进行聚合如求机器的平均cpu user态利用率 avg(rate(…

概率论基础知识点公式汇总

1 概率论的基本概念 1.1 随机事件 样本空间 S S S&#xff1a;将随机实验所有可能的记过组成的集合称为样本空间。样本点&#xff1a;样本空间的每个结果称为样本点。随机试验、随机事件 E E E、基本事件、必然事件、不可能事件、对立事件 A A ‾ A\overline{A} AA、古典概型…

设计模式01:创建型设计模式之单例、简单工厂的使用情景及其基础Demo

一、单例模式 1.情景 连接字符串管理 2.好处 代码简洁&#xff1a;可全局访问连接字符串。性能优化&#xff1a;一个程序一个连接实例&#xff0c;避免反复创建对象&#xff08;连接&#xff09;和销毁对象&#xff08;连接&#xff09;。线程安全&#xff1a;连接对象不会…

Halcon例程代码解读:安全环检测(附源码|图像下载链接)

安全环检测核心思路与代码详解 项目目标 本项目的目标是检测图像中的安全环位置和方向。通过形状匹配技术&#xff0c;从一张模型图像中提取安全环的特征&#xff0c;并在后续图像中识别多个实例&#xff0c;完成检测和方向标定。 实现思路 安全环检测分为以下核心步骤&…

GitCode 光引计划投稿|智能制造一体化低代码平台 Skyeye云

随着智能制造行业的快速发展&#xff0c;企业对全面、高效的管理解决方案的需求日益迫切。然而&#xff0c;传统的开发模式往往依赖于特定的硬件平台&#xff0c;且开发过程繁琐、成本高。为了打破这一瓶颈&#xff0c;Skyeye云应运而生&#xff0c;它采用先进的低代码开发模式…

CSS系列(31)-- Backdrop Filter详解

前端技术探索系列&#xff1a;CSS Backdrop Filter详解 &#x1f3a8; 致读者&#xff1a;探索现代UI效果的艺术 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS Backdrop Filter&#xff0c;这个强大的背景处理特性。 基础效果 &#x1f680; 模糊效果 …

C#-调用C++接口

一.静态&动态装载DLL C中接口通过编译为DLL对外提供调用,C#需要将DLL加载至本应用才可实现C接口调用. 1.静态装载 C#应用程序在编译为可执行exe时将外部DLL装载至本应用中,例如在CSC编译指令中添加相关参数可实现DLL引用. csc /reference:user32.dll /out:HelloWorld.exe 2…