Redis 缓存主要缓存穿透、缓存击穿与缓存雪崩异常场景,今天我们来讲讲缓存穿透。

1 场景描述

缓存穿透是指客户端请求一个缓存和数据库中都不存在的 key。由于缓存中不存在,所以请求会透过缓存查询数据库;由于数据库中也不存在,所以也没办法更新缓存。因此下一次同样的请求还是会打在数据库上。

好像缓存被穿透了一样,缓存形如虚设。所有的压力都在数据库之上,如果请求量巨大,可能造成数据库崩溃。

2 解决方法

缓存穿透有以下几种解决方法。

2.1 接口校验

在请求入口进行校验,比如对用户进行鉴权、数据合法性检查等操作,这样可以减少缓存穿透发生的概率。

这种方式减轻了对 Redis 以及数据库的压力,但是增加了客户端的编码与维护的工作量。如果请求的入口很多,那么工作量很大。

2.2 缓存空值

当缓存与数据库中都没有 key 时,就设置一个空值写入缓存,并同时设置一个比较短的过期时间。由于在缓存中设置空值,所以请求在缓存这一级别就返回,也就不会被穿透。这些所说的不会被穿透只是针对某个 key 而言的。其它没有设置空值的 key,仍然存在被穿透的可能。

该方法的问题是:由于不存在的 key 几乎是无限,不可被穷举的,所以不可能都设置到缓存中。而且大量这样的空值 key 设置到缓存,也会占用大量的内存空间。

解决:采用下面提到的布隆过滤器直接过滤掉不存在的 key。

2.3 布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于判断一个元素是否在一个集合中2

布隆过滤器的特点是判断为不存在的,则一定不存在;判断存在的,则大概率存在。

2.3.1 布隆过滤器原理

布隆过滤器的原理是当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想3

假设有这样一个集合 S,它包含 a b c 三个元素。那么布隆过滤器会利用多个哈希函数(图中是三个哈希函数 h1、h2、h3)来计算所在的位,然后将该位设置为 1。比如元素 a,经过三个哈希函数计算后,将相应的位设置为 1,也就是图中的红线。元素 b 和 c 也是按照相应的方法进行计算处理。这时布隆过滤器初始化完毕。

假设有一个元素 d,需要判断它是否在我们刚才所创建的布隆过滤器中(图中的黄色线条)。经过三个哈希函数 h1、h2、h3 计算后,发现相应的位都是 1,布隆过滤器会返回 true。也就是认为这个元素可能在,也可能不在集合中。看到这里,有同学就会问:“既然这个布隆过滤器都不知道这个元素是不是在集合中,对我们有什么用呢?”

布隆过滤器的强大之处是可以利用较小的缓存,就可以判断出某个元素是否不在集合中。比如又来了一个元素 e,经过三个哈希函数 h1、h2、h3 计算后,发现 h1(e) 所对应的位是 0。那么这个元素 e 肯定不在集合中。有同学又说了:“我用 HashMap 不是也能判断出某个元素在不在集合中呀?”

HashMap 是可以判断,但需要存储集合中所有的元素。如果集合中有上亿个元素,那么就会占用大量的内存。内存空间毕竟是有限,可能还不一定放的下这么多的元素。与 HashMap 相比, 布隆过滤器占用的空间很小,所以很适合判断大集合中某个元素是否不存在。

2.3.2 布隆过滤器误识别率

之前的示例中可以看出,布隆过滤器判断为不存在的元素,则一定不存在;而判断存在的元素,则大概率存在。也就是说,有的元素可能并不在集合中,但是布隆过滤器会认为它存在。这就涉及到一个概念:误识别率。误识别率指的错误判断一个元素在集合中的概率。

假设布隆过滤器有 m bit 大小,需要放入 n 个元素,每个元素使用 k 个哈希函数,那么它的误识别率如下表所示4


2.3.3 使用布隆过滤器

Google 的 Guava 库提供了使用布隆过滤器的 API 类(BloomFilter.class),它是线程安全的。

首先创建布隆过滤器:

//创建存储整型的布隆过滤器
bloomFilter =BloomFilter.create(Funnels.integerFunnel(), expectedInsertions, fpp);

创建布隆过滤器的方法有以下几个入参:

入参 说明
Funnels 实例 用于后续把类对象转换为相应的 hash 值。
expectedInsertions 期望插入过滤器的元素个数。
fpp 误识别率,该值必须大于 0 且小于 1.0。

Funnel 类定义了如何把一个具体的对象类型分解为原生字段值,从而将值分解为 Byte 以供后面 BloomFilter 进行 hash 运算5。也就是说 Guava 的布隆过滤器会根据Funnel 类的定义,计算一个对象的哈希值,放入过滤器。

Guava 官方提供了这样一个创建可插入自定义类的布隆过滤器示例。

首先创建一个 Person 类:

@Data
@AllArgsConstructor
public class Person {private String firstName;private String lastName;private int age;
}

然后创建一个 PersonFunnel 类,它实现了 Funnel 类中的 funnel(Person from, PrimitiveSink into) 方法:

public class PersonFunnel implements Funnel<Person> {@Overridepublic void funnel(Person from, PrimitiveSink into) {into.putUnencodedChars(from.getFirstName()).putUnencodedChars(from.getLastName()).putInt(from.getAge());}
}

这个方法主要是把 Person 类中的各个属性(名字、年龄)写入 PrimitiveSink 对象。 PrimitiveSink 提供了支持各种写入类型的方法:

接着把元素放入布隆过滤器:

bloomFilter.put(new Person("deniro","lee",20));
bloomFilter.put(new Person("lily","lee",16));

最后就是判断某个元素是否在布隆过滤器中:

Assert.assertFalse(bloomFilter.mightContain(new Person("jack","lee",20)));
Assert.assertTrue(bloomFilter.mightContain(new Person("deniro","lee",20)));

Funnels 是个工具类,内置了一些创建基本类型的 Funnel:

我们可以利用这些 Funnel,来创建包含基本类型元素的布隆过滤器,比如创建一个包含整型元素的布隆过滤器:

 BloomFilter<Integer> bloomFilter =BloomFilter.create(Funnels.integerFunnel(), size, fpp);

最后,让我们总结一波。

可以看到接口校验方法与 Guava 版的布隆过滤器方法都是在客户侧进行处理。布隆过滤器也可以在缓存层进行处理。相对来说,布隆过滤器方法比接口校验方法少了很多代码量与维护成本。缓存空值不可取,毕竟内存空间是有限的。

利用布隆过滤器,我们可以拦截绝大多数不存在的 key,因此很适合解决 Redis 缓存穿透问题。


参考资料:

  1. 缓存穿透、缓存击穿、缓存雪崩解决方案
  2. 布隆过滤器
  3. 大数据量下的集合过滤—Bloom Filter
  4. 吴军. 数学之美.第2版[M]. 人民邮电出版社, 2014. p208-209.
  5. 结合Guava源码解读布隆过滤器

说说 Redis 缓存穿透场景与相应的解决方法相关推荐

  1. Redis 缓存穿透、击穿、雪崩 解决方法

    目录 引言 一.缓存穿透 1. 缓存穿透的原理 2. 解决方法 2.1 布隆过滤器 2.2 缓存空对象 二.缓存击穿 1. 缓存击穿原理 2. 解决方法 2.1 设置热点数据永不过期 2.2 加互斥锁 ...

  2. 配置redis缓存实例出现报错 及解决方法

    redis由于其高并发和高性能常被用于缓存 原理图如下 配置时常常会遇到一些问题. 正常情况下,访问应该如下展示: 第一次访问页面 再次刷新上次访问页面,会出现如下页面 mysql 变为redis 但 ...

  3. Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略

    1. Redis 缓存穿透 1.1 Redis 缓存穿透概念 访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常. 由于不恰当的业务功能实现,或者外部恶 ...

  4. 布隆过滤器Redis缓存穿透雪崩击穿热点key

    目录 布隆过滤器 Redis 缓存 穿透 雪崩 击穿 热点KEY 布隆过滤器 布隆过滤器(判断某个key一定不存在) 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构 特点是高效地插入和查询, ...

  5. Redis系列(二):Redis缓存穿透和缓存雪崩是什么?

    一.Redis穿透 缓存穿透现象:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询.发现也没有,于是本次查询失败.当用户很多的时候,缓存都没有命中,于是都 ...

  6. 解决Redis缓存穿透之布隆过滤器详解

    文章目录 1. 什么是Bloom Filter(布隆过滤器) 1.1 布隆过滤器优点 1.2 布隆过滤器缺点 1.3 布隆过滤器使用场景 1.4 布隆过滤器检索过程 1.5 布隆过滤器的算法描述 2. ...

  7. Redis缓存穿透方案

    转自:https://blog.csdn.net/muyi_amen/article/details/80229647 解决Redis缓存穿透方案 缓存技术可以用来减轻数据库的压力,提升访问效率.目前 ...

  8. Redis 缓存穿透、缓存击穿和缓存雪崩

    目录 Redis 缓存穿透.缓存击穿和缓存雪崩 Redis 缓存穿透(查不到) Redis缓存穿透的解决方案 方案一:接口校验 方案二:缓存空对象 方案三:布隆过滤器 Redis 缓存击穿 Redis ...

  9. Redis缓存穿透问题及解决方案

    Redis缓存穿透问题及解决方案 参考文章: (1)Redis缓存穿透问题及解决方案 (2)https://www.cnblogs.com/lingyejun/p/10087135.html 备忘一下 ...

最新文章

  1. JavaEE路径陷阱之getRealPath
  2. 7-30 字符串的冒泡排序 (C语言)
  3. java 实现约瑟夫环
  4. VTK:网格之CellEdges
  5. IOS开发基础之使用AFNetworking框架下载服务器资源图片
  6. 巴菲特:伯克希尔没有所谓的5G核心能力 子公司会涉足相关行业
  7. 上海电子信息职业技术学院计算机网络技术,上海电子信息职业技术学院计算机网络技术专业...
  8. 查看html元素绑定的事件与方法 visual Event 插件
  9. zk 加载 html 标签,HTML字符实体——ZK支持字符
  10. win7系统移动磁盘合并卷
  11. 倩女幽魂服务器维修什么意思,《倩女幽魂》2月7日游戏服务器维护内容
  12. c语言if语句知识点总结,c语言中if语句知识点总结
  13. Python 数独求解
  14. Linux Deamon函数
  15. linux批量修改图片大小,Linux中使用Imagemagick批量调整图片大小
  16. 弱占优策略--Weakly Dominant Strategy
  17. dos下拷贝服务器文件命令行,win7在DOS环境下怎么使用copy命令?使用copy命令复制文件的方法...
  18. java自旋锁使用_10.Java锁之自旋锁
  19. Python3 数据库(MySQL/MongoDB/Redis)
  20. Springboot旅游网的设计与实现xb29f计算机毕业设计-课程设计-期末作业-毕设程序代做

热门文章

  1. office安装失败,注册表1402问题的终极解决方法。
  2. Dosbox窗口调整大小
  3. 啥是佩琪?Python画佩琪
  4. python做的小项目实例_?【清单】精选10个适合新手练习的 Python 实战小项目
  5. Python学习之---open操作+buffering缓冲区+上下文管理+StringIO和BytesIO
  6. 英语流利说 第28天
  7. 2020济南ICPC J.Tree Constructer(二分图,构造)
  8. python调用php_python如何调用php文件中的函数详解
  9. XiaomiRouter自学之路(01-项目背景)
  10. android studio oncreate,android studio怎样实现 oncreate方法