当前位置: 首页 > 科技观察

牛逼的布隆过滤器有什么用?

时间:2023-03-22 15:41:59 科技观察

图片来自宝兔网本文将介绍Bloomfilter,以空间换时间,以更低的内存空间高效解决这个问题。性能不够,用缓存来弥补。现在,年轻人喜欢在网上购物。没事的时候可以去淘宝逛一逛,买点自己喜欢的,释放一下工作压力。地址:https://detail.tmall.com/item.htm?id=628993216729上图为天猫iPhone12商品详情页,id为商品序列号。我们都知道淘宝的访问量是非常高的。为了提高系统的吞吐量,做了很多性能优化。其中最重要的一点是将信息异构存储到缓存中。有句话说得好:性能不够,缓存可以弥补。但是,在使用缓存的时候,我们要注意一个重要的问题。如果缓存没有命中怎么办?缓存不命中怎么办?如上图所示:我们首先查询缓存,判断缓存中是否有数据。如果有数据,直接返回。如果cache为Empty,则需要再次查询数据库,将数据格式异构化,然后预热到buffer中,然后返回结果。注:步骤③存在风险漏洞。如果缓存中的数据不存在,压力就会转移到数据库中。如果被竞争对手利用进行无效请求流量攻击,瞬间向数据库发送大量请求,对系统性能影响很大,很容易挂掉数据库。这种现象称为缓存穿透。那么如何应对缓存穿透呢?我们的思路是在缓存中判断这个数据库值是否存在。如果真的不存在,直接返回,避免数据库查询。由于不存在是无限边界,我们采用反向策略来建立对现有值的有效检索。每次缓存一个值时,首先执行一次空搜索。简单总结一下,这个框架的要求是:检索速度快,内存空间非常小经过研究,我们发现Bloomfilters满足了以上两个条件。什么是布隆过滤器?BloomFilter是Bloom在1970年提出的,它实际上是一个长二值向量和一系列随机映射函数。布隆过滤器可用于检索元素是否在集合中:优点:空间效率和查询时间远超一般算法。缺点:有一定的误识别率,删除难度大。布隆过滤器是如何构造的?布隆过滤器本质上是一个n位的二进制数组,由0和1表示。以商品为例,共有三种商品,商品编码分别为id1、id2、id3。①首先对id1进行3次哈希,确定其在二进制数组中的位置。三个哈希,对应的二进制数组下标分别为2、5、8,将原始数据从0变为1。②对id2进行3次哈希,确定其在二进制数组中的位置。三个哈希,对应的二进制数组下标分别为2、7、98,将原来的数据从0变为1。下标2已经被之前的操作设置为1,所以这次认为是哈希碰撞,并没有需要改变。哈希规则:如果哈希后原位为0,则由0变为1;如果该位本身为1,则保持不变。如何使用布隆过滤器?如下图所示:与初始化过程类似。在查询商品的缓存信息时,首先要判断商品是否存在:通过三个哈希函数计算商品id的哈希值,然后在Bloom数组中查找并访问对应的位值,判断是否为0或者1,只要这三个值有一个不为1,那么我们就认为数据不存在。注意:布隆过滤器只能准确判断数据是否缺失。只能说是数据存在的可能,因为存在Hash冲突。当然,这个概率很低。如何减少布隆过滤器的误判?①增加二进制位数组的长度。这样数据经过散列后会更加离散,发生冲突的概率会大大降低。②变相增加哈希数,增加数据特征。特征越多,冲突的概率越低。布隆过滤器会不会消耗大量内存?带着疑惑,我们来做个实验:假设有1000万条数据,我们需要记录是否存在。如果存在则标记为1,如果不存在则标记为0。在技术选型上,框架使用了Redis的BitMap存储。数据初始化预热代码:redisTemplate.executePipelined(newRedisCallback(){@Nullable@OverridepublicLongdoInRedis(RedisConnectionconnection)throwsDataAccessException{connection.openPipeline();for(intoffset=10000000;offset>=0;offset--){booleanvalue=offset%2==0?true:false;connection.setBit("bloom-filter-data-1".getBytes(),offset,value);}connection.closePipeline();returnull;}});System.out.println("数据预热完成");性能有点慢,我们也可以采用分组的形式,10000个数字一组,分批提交。数据上传后大小为1.19M,和我们想象的一样。计算公式:10000000/8/1024/1024=1.19MJava应用,如何使用布隆过滤器?Java语言的生态非常繁荣,提供了很多开箱即用的开源框架供我们使用。布隆过滤器也不例外。Java提供了一个带有内置布隆过滤器的Redisson组件。首先引入依赖包:org.redissonredisson3.11.1代码示例:/***@author*/@Testpublicvoidtest5(){Configconfig=newConfig();config.useSingleServer().setAddress("redis://172.16.67.37:6379");RedissonClientcient=Redisson.create(config);RBloomFilterbloomFilter=cient.getBloomFilter("test5-bloom-filter");//初始化布隆过滤器,数组长度为100W,误报率为1%bloomFilter.tryInit(1000000L,0.01);//添加数据bloomFilter.add("汤姆哥");//判断是否有System.out.println(bloomFilter.contains("微技术"));System.out.println(bloomFilter.contains("TomBrother"));}运行结果:false//肯定不存在true//可能存在,有1%的误报率注意:如果误报率为设置太小,会产生较多的Hash运算,降低系统性能。通常我们的建议值为1%。如何删除布隆过滤器二进制数组?初始化后的布隆过滤器可以直接使用。但是,如果删除了原始数据怎么办?如何维护布隆过滤器二进制数组?不能直接删除吗?真的不!因为存在Hash冲突的可能,会导致误删。我应该怎么办?方案一:开发一个定时任务,每隔几个小时自动创建一个新的Bloomfilter数组,并替换旧的,有点像CopyOnWriteArrayList。方案二:在Bloomfilter中加入一个等长数组来存放计数器,主要是为了解决冲突问题。每删除一次相应的计数器就减一。如果结果为0,则将主数组的二进制值更新为0。布隆过滤器的应用场景如下:本文重点解决缓存穿透。网络爬虫对URL进行去重,避免抓取相同的URL地址。反垃圾邮件,从数十亿个垃圾邮件列表中判断一个邮箱是否为垃圾邮件邮箱。作者:汤姆哥编辑:陶家龙来源:转载自公众号微科技(ID:weiguanjishu)