ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【架构师面试-缓存与搜索-2】-基于布隆过滤器解决缓存穿透

2021-12-16 22:02:19  阅读:134  来源: 互联网

标签:缓存 映射 元素 布隆 哈希 过滤器 架构师


1:什么是缓存穿透

一个常见的缓存使用方式:读请求来了,先查下缓存,缓存有值命中,就直接返回;缓存没命中,就去查数据库,然后把数据库的值更新到缓存,再返回。

缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

大量数据判断是否存在

这个中间层,是不是用HashMap就好了呢?听起来不错嘛,HashMap时间复杂度可以达到O(1),但是呢因为HashMap数据是在内存里面的,如果大量的数据远超出了服务器的内存呢,那就无法使用HashMap啦,可以使用布隆过滤器来做这个缓冲的事情。

2:布隆过滤器使用场景

布隆过滤器主要是在redis中问的比较多,因此像这种数据结构类的主要是考原理以及使用场景。

1:使用场景

1. 钓鱼网站

2. 垃圾邮件检测

3. 解决缓存穿透:通常使用布隆过滤器去解决redis中的缓存穿透:解决方案是redis中bitmap的实现

先举一个例子,在我们身边充斥着各种各样的XX网站,为了不毒害我们祖国的花朵,于是国家网警就开始对这些网站进行割除过滤,问题来了,这些网站的地址其实是不停的更换的,这些垃圾网站和正常网站加起来全世界据统计也有几十亿个。因此就会带来如下的问题:

(1)网站数量太多,存储起来比较麻烦。一个地址最起码有32个字节,一亿个地址就需要1.6G的内存。

(2)一个一个比较,太费时间了。

因此布隆过滤器被设计出来了,他是如何做到高效的呢?本质上其实就是一个HASH映射器。他的底层其实是一个超大的二进制向量和一系列随机映射函数。现在我们按照之前的那个例子,我们存储1亿个垃圾网站地址。

第一步:建立一个32亿二进制(比特),也就是4亿字节的向量。全部置0

第二步:网警用八个不同的随机数产生器(F1,F2, …,F8) 产生八个信息指纹(f1, f2, …, f8)。

第三步:用一个随机数产生器 G 把这八个信息指纹映射到 1 到32亿中的八个自然数 g1, g2, …,g8。

第四步:把这八个位置的二进制全部设置为一。

OK,有一天网警查到了一个可疑的网站,想判断一下是否是XX网站,于是就开始检查了。通过同样的方法将XX网站通过哈希映射到32亿个比特位数组上的8个点。如果8个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。

注意:如果两个XX网站通过上面的步骤映射到了相同的8个点上,或者是有一部分点是重合的,这时候该怎么办?于是就出现了误报,也就是说A网站在12345678个点上全部置1,B网站通过同样的方式在23456789上全部置1,这时候B网站来了是不能确定是否包含的。这个逻辑相信各位都理解。这个是最基础的面试问题。

3:布隆过滤器是个啥?

布隆过滤器其实就是加快判定一个元素是否在集合中出现的方法。

考点:布隆过滤器只能判定一个元素不在集合里面,不能判断存在!什么意思呢,就是说一个苹果不在篮子里,这个我可以通过布隆过滤器知道,但是一定在篮子里嘛?这个通过布隆过滤器我是不能判定的。

1、怎么理解布隆过滤器?

布隆过滤器是一种占用空间很小的数据结构,它由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个集合中,空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关 。

2、布隆过滤器原理

假设我们有个集合A,A中有n个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为a位的数组B中的不同位置上,这些位置上的二进制数均设置为1。如果待检查的元素,经过这k个哈希散列函数的映射后,发现其k个位置上的二进制数全部为1,这个元素很可能属于集合A,反之,一定不属于集合A。

来看个简单例子吧,假设集合A有3个元素,分别为{d1,d2,d3}。有1个哈希函数,为Hash1。现在将A的每个元素映射到长度为16位数组B。

我们现在把d1映射过来,假设Hash1(d1)= 2,我们就把数组B中,下标为2的格子改成1,如下:

我们现在把d2也映射过来,假设Hash1(d2)= 5,我们把数组B中,下标为5的格子也改成1,如下:

接着我们把d3也映射过来,假设Hash1(d3)也等于 2,它也是把下标为2的格子标1:

因此,我们要确认一个元素dn是否在集合A里,我们只要算出Hash1(dn)得到的索引下标,只要是0,那就表示这个元素不在集合A,如果索引下标是1呢?那该元素可能是A中的某一个元素。因为你看,d1和d3得到的下标值,都可能是1,还可能是其他别的数映射的,布隆过滤器是存在这个缺点的:会存在hash碰撞导致的假阳性,判断存在误差。

3:如何减少这种误差呢?

搞多几个哈希函数映射,降低哈希碰撞的概率

同时增加B数组的bit长度,可以增大hash函数生成的数据的范围,也可以降低哈希碰撞的概率

我们又增加一个Hash2哈希映射函数,假设Hash2(d1)=6,Hash2(d3)=8,它俩不就不冲突了嘛,如下:

即使存在误差,我们可以发现,布隆过滤器并没有存放完整的数据,它只是运用一系列哈希映射函数计算出位置,然后填充二进制向量。如果数量很大的话,布隆过滤器通过极少的错误率,换取了存储空间的极大节省,还是挺划算的。

开源实现:

Google的Guava类库

Twitter的 Algebird 类库

4、关于误报率

通过上面的解释相信都大概了解的差不多了,其实就是hash函数映射,由于有hash冲突产生了误报率,

误报率也就是判断失败的情况。

既然是由于hash冲突,那我把布隆过滤器的二进制向量调到很大,这样不就解决了嘛,但是由于数据量比较大,因此现在就要考虑一下误报率和存储效率之间选择一个折中值了。有一个计算公式如下:公式来源于github

假设位数组的长度为m,哈希函数的个数为k。检测某一元素是否在该集合中的误报率是:

如何使得误报率最小,数学问题,求导就可以了。

4:代码实现布隆过滤器

上面只是给出了其原理,下面我们代码实现一下。

/**
* 自定义布隆过滤器
*/
public class MyBloomFilter {
// 2 << 25表示32亿个比特位
private static final int DEFAULT_SIZE = 2 << 25;
private static final int[] seeds = new int[]{3, 5, 7, 11, 13, 19, 23,
37};
//这么大存储在BitSet
private BitSet bits = new BitSet(DEFAULT_SIZE);
private SimpleHash[] func = new SimpleHash[seeds.length];
public static void main(String[] args) {
//可疑网站
String value = "www.kkb.com";
MyBloomFilter filter = new MyBloomFilter();
//加入之前判断一下
System.out.println(filter.contains(value));
filter.add(value);
//加入之后判断一下
System.out.println(filter.contains(value));
}
//构造函数
public MyBloomFilter() {
for (int i = 0; i < seeds.length; i++) {
func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
//添加网站
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
//判断可疑网站是否存在
public boolean contains(String value) {
if (value == null) {
return false;
}
boolean ret = true;
for (SimpleHash f : func) {
//核心就是通过“与”的操作
ret = ret && bits.get(f.hash(value));
}
return ret;
}
}

还有一个SimpleHash,我们看一下

/**
* 简单的哈希函数实现
*/
public class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
public int hash(String value) {
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++) {
result = seed * result + value.charAt(i);
}
return (cap - 1) & result;
}
}

如果您觉得文章好看,欢迎点赞收藏加关注,一连三击呀,感谢!!☺☻ 

标签:缓存,映射,元素,布隆,哈希,过滤器,架构师
来源: https://blog.csdn.net/chongfa2008/article/details/121973683

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有