说说 Redis 缓存穿透场景与相应的解决方法
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 缓存穿透问题。
参考资料:
- 缓存穿透、缓存击穿、缓存雪崩解决方案
- 布隆过滤器
- 大数据量下的集合过滤—Bloom Filter
- 吴军. 数学之美.第2版[M]. 人民邮电出版社, 2014. p208-209.
- 结合Guava源码解读布隆过滤器
说说 Redis 缓存穿透场景与相应的解决方法相关推荐
- Redis 缓存穿透、击穿、雪崩 解决方法
目录 引言 一.缓存穿透 1. 缓存穿透的原理 2. 解决方法 2.1 布隆过滤器 2.2 缓存空对象 二.缓存击穿 1. 缓存击穿原理 2. 解决方法 2.1 设置热点数据永不过期 2.2 加互斥锁 ...
- 配置redis缓存实例出现报错 及解决方法
redis由于其高并发和高性能常被用于缓存 原理图如下 配置时常常会遇到一些问题. 正常情况下,访问应该如下展示: 第一次访问页面 再次刷新上次访问页面,会出现如下页面 mysql 变为redis 但 ...
- Redis 缓存穿透、雪崩、缓存数据库不一致、持久化方式、分布式锁、过期策略
1. Redis 缓存穿透 1.1 Redis 缓存穿透概念 访问了不存在的 key,缓存未命中,请求会穿透到 DB,量大时可能会对 DB 造成压力导致服务异常. 由于不恰当的业务功能实现,或者外部恶 ...
- 布隆过滤器Redis缓存穿透雪崩击穿热点key
目录 布隆过滤器 Redis 缓存 穿透 雪崩 击穿 热点KEY 布隆过滤器 布隆过滤器(判断某个key一定不存在) 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构 特点是高效地插入和查询, ...
- Redis系列(二):Redis缓存穿透和缓存雪崩是什么?
一.Redis穿透 缓存穿透现象:用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询.发现也没有,于是本次查询失败.当用户很多的时候,缓存都没有命中,于是都 ...
- 解决Redis缓存穿透之布隆过滤器详解
文章目录 1. 什么是Bloom Filter(布隆过滤器) 1.1 布隆过滤器优点 1.2 布隆过滤器缺点 1.3 布隆过滤器使用场景 1.4 布隆过滤器检索过程 1.5 布隆过滤器的算法描述 2. ...
- Redis缓存穿透方案
转自:https://blog.csdn.net/muyi_amen/article/details/80229647 解决Redis缓存穿透方案 缓存技术可以用来减轻数据库的压力,提升访问效率.目前 ...
- Redis 缓存穿透、缓存击穿和缓存雪崩
目录 Redis 缓存穿透.缓存击穿和缓存雪崩 Redis 缓存穿透(查不到) Redis缓存穿透的解决方案 方案一:接口校验 方案二:缓存空对象 方案三:布隆过滤器 Redis 缓存击穿 Redis ...
- Redis缓存穿透问题及解决方案
Redis缓存穿透问题及解决方案 参考文章: (1)Redis缓存穿透问题及解决方案 (2)https://www.cnblogs.com/lingyejun/p/10087135.html 备忘一下 ...
最新文章
- JavaEE路径陷阱之getRealPath
- 7-30 字符串的冒泡排序 (C语言)
- java 实现约瑟夫环
- VTK:网格之CellEdges
- IOS开发基础之使用AFNetworking框架下载服务器资源图片
- 巴菲特:伯克希尔没有所谓的5G核心能力 子公司会涉足相关行业
- 上海电子信息职业技术学院计算机网络技术,上海电子信息职业技术学院计算机网络技术专业...
- 查看html元素绑定的事件与方法 visual Event 插件
- zk 加载 html 标签,HTML字符实体——ZK支持字符
- win7系统移动磁盘合并卷
- 倩女幽魂服务器维修什么意思,《倩女幽魂》2月7日游戏服务器维护内容
- c语言if语句知识点总结,c语言中if语句知识点总结
- Python 数独求解
- Linux Deamon函数
- linux批量修改图片大小,Linux中使用Imagemagick批量调整图片大小
- 弱占优策略--Weakly Dominant Strategy
- dos下拷贝服务器文件命令行,win7在DOS环境下怎么使用copy命令?使用copy命令复制文件的方法...
- java自旋锁使用_10.Java锁之自旋锁
- Python3 数据库(MySQL/MongoDB/Redis)
- Springboot旅游网的设计与实现xb29f计算机毕业设计-课程设计-期末作业-毕设程序代做
热门文章
- office安装失败,注册表1402问题的终极解决方法。
- Dosbox窗口调整大小
- 啥是佩琪?Python画佩琪
- python做的小项目实例_?【清单】精选10个适合新手练习的 Python 实战小项目
- Python学习之---open操作+buffering缓冲区+上下文管理+StringIO和BytesIO
- 英语流利说 第28天
- 2020济南ICPC J.Tree Constructer(二分图,构造)
- python调用php_python如何调用php文件中的函数详解
- XiaomiRouter自学之路(01-项目背景)
- android studio oncreate,android studio怎样实现 oncreate方法