缓存穿透场景解决与布隆过滤器的使用

简介: 缓存穿透场景解决与布隆过滤器的使用

1 缓存使用场景

有些数据查询频率很高的时候,我们会将数据存入到缓存,用户每次查询直接查询缓存即可,从而提高用户访问数据的效率。

比如获取用户为 lisi 的抢红包记录,此时如果每次查询数据库效率都很低,我们可以第1次从数据库查询 lisi 最近的前10条抢红包记录,然后将记录存入到Redis缓存,下次直接查询Redis缓存即可。

每次用户抢红包,谁抢到了红包,我们会将抢到红包的用户信息按照抢红包的金额大小的前100名用户信息公示出去,这里也可以采用这种方式来做。

当然,也不是所有数据都适合做缓存,需要根据数据特点来决定,如下图:


b51acc2a237c44f1915f91411f0e806f.png

2 缓存穿透介绍

c75b6e7f2d1c4f65b605cce34cb937d1.png

上面查询最近红包记录提升用户访问效率,这种操作是一种正常操作,但也存在一些非正常操作,比如 wangwu 没有抢到红包,但用户恶意频繁去查询抢红包记录,此时Redis缓存中将一直没有数据,每次都会查询数据库,这种现象叫缓存穿透。缓存穿透该如何解决呢?我们提供这一种思路,如下图所示:

53a6eaa4c6d440bfb1d111e1a0134c6a.png


3 穿透问题解决

e6b1bf59112647c3a27deb62741c4ca2.png


4 布隆过滤器

防止缓存穿透,有多种方案,上面所实现的是一种最简单的方案,也有其他方案,比如布隆过滤器也是缓存穿透方案之一。布隆过滤器主要是解决大规模数据下不需要精确过滤的业务场景,如检查垃圾邮件地址,爬虫URL地址去重,解决缓存穿透问题等。

布隆过滤器:在一个存在一定数量的集合中过滤一个对应的数据,判断该数据是否在该集合中。


4.1 原理

布隆过滤器的巨大用处就是,能够迅速判断一个元素是否在一个集合中。因此他有如下三个使用场景:


1.网页爬虫对URL的去重,避免爬取相同的URL地址

2.反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)

3.缓存穿透,将所有可能存在的数据缓存放到布隆过滤器中,当黑客访问不存在的缓存时迅速返回避免缓存及DB挂掉。

我们来谈谈布隆过滤器的原理

其内部维护一个全为0的bit数组,需要说明的是,布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。

假设,根据误判率,我们生成一个10位的bit数组,以及2个hash函数((f_1,f_2)),如下图所示(生成的数组的位数和hash函数的数量,我们不用去关心是如何生成的,有数学论文进行过专业的证明)。

3b3e8eaaac1541f9a33bab29bf28e9e2.png

假设输入集合为((N_1,N_2)),经过计算(f_1(N_1))得到的数值得为2,(f_2(N_1))得到的数值为5,则将数组下标为2和下表为5的位置置为1,如下图所示

352a7233c5ce48fb8524b917f835e7fe.png

同理,经过计算(f_1(N_2))得到的数值得为3,(f_2(N_2))得到的数值为6,则将数组下标为3和下表为6的位置置为1,如下图所示

d4b322ff4c6f4884849bdcddff6823c7.png

这个时候,我们有第三个数(N_3),我们判断(N_3)在不在集合((N_1,N_2))中,就进行(f_1(N_3),f_2(N_3))的计算

4.若值恰巧都位于上图的红色位置中,我们则认为,(N_3)在集合((N_1,N_2))中

5.若值有一个不位于上图的红色位置中,我们则认为,(N_3)不在集合((N_1,N_2))中

以上就是布隆过滤器的计算原理。

4.2 布隆过滤器案例

引入依赖包

<!--google的布隆过滤器-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>

编写测试类

public class BloomFilterTest {
    private static int size = 1000000;
  //Google的布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001);
    public static void main(String[] args) {
    //放一百万个key到布隆过滤器中 1000000
    for (int i = 0; i < size; i++) {
      bloomFilter.put(i);
   }
    List<Integer> list = new ArrayList<Integer>(1000);
    //取10000个不在过滤器里的值,看看有多少个会被认为在过滤器里
    for (int i = size + 10000; i < size + 20000; i++) {
      if (bloomFilter.mightContain(i)) {
        list.add(i);
     }
   }
    System.out.println("误判的数量:" + list.size());
 }
}

我们可以发现有330个被误判,误判的概率为0.03,源码中也有说明。

905d7aa0201a48d69752f2e92244502a.png

这里的误判概率是可以调整的,每次创建 BloomFilter 的时候,指定误判概率值即可,这个值必须大于0。

ca903dd22ba24cf1b43ca9c20a5c0d22.png

优点


思路简单

保证一致性

性能强

缺点

代码复杂度增大

需要另外维护一个集合来存放缓存的Key

布隆过滤器不支持删值操作


目录
相关文章
|
26天前
|
缓存 NoSQL Java
避免缓存失效的三大杀手:缓存击穿、穿透与雪崩的解决方案
避免缓存失效的三大杀手:缓存击穿、穿透与雪崩的解决方案
30 0
|
2月前
|
缓存 监控 NoSQL
redis 缓存穿透 击穿 雪崩 的原因及解决方法
redis 缓存穿透 击穿 雪崩 的原因及解决方法
|
7天前
|
canal 缓存 NoSQL
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;先删除缓存还是先修改数据库,双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
Redis常见面试题(一):Redis使用场景,缓存、分布式锁;缓存穿透、缓存击穿、缓存雪崩;双写一致,Canal,Redis持久化,数据过期策略,数据淘汰策略
|
16天前
|
缓存 NoSQL Redis
使用Redis实现缓存穿透的解决方案
使用Redis实现缓存穿透的解决方案
|
2天前
|
存储 缓存 Java
反向代理缓存是什么,它适用于哪些场景
反向代理缓存是什么,它适用于哪些场景
|
28天前
|
存储 缓存 监控
详解缓存雪崩、缓存击穿、缓存穿透问题,一文掌握,干货不断
详解缓存雪崩、缓存击穿、缓存穿透问题,一文掌握,干货不断
|
11天前
|
缓存 数据库
高并发场景下,到底先更新缓存还是先更新数据库?
高并发场景下,到底先更新缓存还是先更新数据库?
|
15天前
|
缓存 NoSQL Redis
使用Redis实现缓存穿透的解决方案
使用Redis实现缓存穿透的解决方案
|
2月前
|
缓存 数据库 NoSQL
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
【5月更文挑战第16天】该方案提出了解决Redis缓存穿透、击穿和雪崩问题的策略。通过使用两个或多个互为备份的Redis集群,确保在单个集群故障时,另一个可以接管。在故障发生时,业务会与备用集群保持心跳检测,并根据业务重要性分批转移流量,逐步增加对备用集群的依赖,同时监控系统稳定性。对于成本敏感的小型公司,可以采用低成本的单机或小规模自建Redis备份。此方案强调渐进式流量转移,以保护系统免受突然压力冲击。
32 1
【后端面经】【缓存】35|缓存问题:怎么解决缓存穿透、击穿和雪崩问题?--主从切换方案
|
26天前
|
缓存 NoSQL Java
Redis系列学习文章分享---第四篇(Redis快速入门之Java客户端--商户查询缓存+更新+双写一致+穿透+雪崩+击穿+工具封装)
Redis系列学习文章分享---第四篇(Redis快速入门之Java客户端--商户查询缓存+更新+双写一致+穿透+雪崩+击穿+工具封装)
30 0