IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【架构师面试-缓存与搜索-2】-基于布隆过滤器解决缓存穿透 -> 正文阅读

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

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;
}
}

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-18 15:50:19  更:2021-12-18 15:50:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 10:55:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码