布隆过滤器 java有实际应用吗,设计一个简单的布隆过滤器JAVA

  布隆过滤器 java有实际应用吗,设计一个简单的布隆过滤器JAVA

  

目录

位图Bloom Filter利用场景传统数据结构的不足实现原理实现误判现象。bitmapRedisBloomGuava的BloomFilterRedisson缓存渗透解决方案摘要

 

  00-1010现代计算机使用二进制(位)作为信息的基本单位。一个字节等于八位。举个例子,大字符串是由三个字节组成的,但是在计算机中存储时实际上是用二进制表示的。big对应的ASCII码是98,105,103,对应的二进制码是01100010,01101001,0110。

  很多开发语言都提供了操作位的功能,合理使用位可以有效提高内存利用率和开发效率。

  位图的基本思想是用一个位来标记一个元素的值,而关键就是这个元素。因为用bit来存储数据,所以可以大大节省存储空间。

  在Java中,int占用4个字节,1字节=8位(1字节=8位)。如果我们用这32位中每一位的值来表示一个数,是否可以表示32个数?也就是说,32个数只需要一个int占用的空间,就可以把空间缩小32倍。

  1字节=8位,1 KB=1024字节,1 MB=1024 KB,1GB=1024 MB

  假设网站有1亿用户,每天有5000万用户独立访问。如果每天将活动用户与集合类型和位图分开存储:

  1.如果用户id为int类型,4字节32位,则set类型占用的空间为50 000 000 * 4/1024/1024=200m;

  2.如果按位存储,5000万这个数就是5000万位,占用空间是5000000/8/1024/1024=6m。

  那么如何用位图来表示一个数字呢?

  上面说了,一个元素对应的值是用bit位标记的,key就是这个元素。我们可以把位图想象成一个基于位的数组.数组的每个单元只能存储0和1(0表示这个数不存在,1表示它存在)。数组的下标在位图中称为偏移量。例如,我们需要将四个数字{1,3,5,7}表示如下:

  如果还有65这个数字呢?你只需要打开int[N/32 1] int数组来存储这些数据(其中N代表这组数据中的最大值),即:

  Int[0]:可以代表0~31。

  Int[1]:可以代表32~63。

  Int[2]:可以代表64~95。

  假设我们要判断任意一个整数是否在列表中,那么M/32会得到下标,M2会知道它在这个下标中的位置,比如:

  6/32=2,652=1,即65是int[2]中的第1位。

  00-1010本质上,Bloom filter是一种数据结构,一种巧妙的概率数据结构,以高效的插入和查询为特征,可以用来告诉你“某个东西一定不存在或者可能存在”。

  与传统的链表、集合和映射等数据结构相比,它效率更高,占用空间更少,但其缺点是返回的结果是概率性的,而不是精确的。

  实际上,布鲁姆过滤器在网页黑名单系统,垃圾邮件过滤系统,爬虫网址判重系统,等地被广泛使用。Google著名的分布式数据库Bigtable使用Bloom filter来查找不存在的行或列,以减少磁盘搜索的IO次数。谷歌Chrome浏览器使用Bloom filter来加速安全浏览服务。

  Bloom filter也用在很多键值系统中来加速查询过程,比如Hbase,Accumulo,Leveldb。一般来说,值存储在磁盘中,访问磁盘需要花费大量时间。而使用Bloom filter可以快速确定某个键对应的值是否存在,这样可以避免很多不必要的磁盘IO操作。

  通过散列函数将元素映射到位数组中的一个点。这样,我们只需要看这个点是否为1就可以知道它是否存在于集合中。这是布隆过滤器的基本思想。

  

BitMap

 

  >1、目前有 10 亿数量的自然数,乱序排列,需要对其排序。限制条件在 32 位机器上面完成,内存限制为 2G。如何完成?

  2、如何快速在亿级黑名单中快速定位 URL 地址是否在黑名单中?(每条 URL 平均 64 字节)

  3、需要进行用户登陆行为分析,来确定用户的活跃情况?

  4、网络爬虫-如何判断 URL 是否被爬过?

  5、快速定位用户属性(黑名单、白名单等)?

  6、数据存储在磁盘中,如何避免大量的无效 IO?

  7、判断一个元素在亿级数据中是否存在?

  8、缓存穿透。

  

 

  

传统数据结构的不足

一般来说,将网页 URL 存入数据库进行查找,或者建立一个哈希表进行查找就 OK 了。

 

  当数据量小的时候,这么思考是对的,确实可以将值映射到 HashMap 的 Key,然后可以在 O(1) 的时间复杂度内 返回结果,效率奇高。但是 HashMap 的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,举个例子如果一个 1000 万 HashMap,Key=String(长度不超过 16 字符,且重复性极小),Value=Integer,会占据多少空间呢?1.2 个 G。

  实际上用 bitmap,1000 万个 int 型,只需要 40M( 10 000 000 * 4/1024/1024 =40M)左右空间,占比 3%,1000 万个 Integer,需要 161M 左右空间,占比 13.3%。

  可见一旦你的值很多例如上亿的时候,那 HashMap 占据的内存大小就可想而知了。

  但如果整个网页黑名单系统包含 100 亿个网页 URL,在数据库查找是很费时的,并且如果每个 URL 空间为 64B,那么需要内存为 640GB,一般的服务器很难达到这个需求。

  

 

  

实现原理

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

 

  比如我们有 3 个 URL{URL1,URL2,URL3},通过一个hash 函数把它们映射到一个长度为 16 的数组上,如下:

  

 

  若当前哈希函数为Hash1(),通过哈希运算映射到数组中,假设Hash1(URL1) = 3Hash1(URL2) = 6Hash1(URL3) = 6,如下:

  

 

  因此,如果我们需要判断URL1是否在这个集合中,则通过Hash(1)计算出其下标,并得到其值若为 1 则说明存在。

  由于 Hash 存在哈希冲突,如上面URL2,URL3都定位到一个位置上,假设 Hash 函数是良好的,如果我们的数组长度为 m 个点,那么如果我们想将冲突率降低到例如1%, 这个散列表就只能容纳m/100个元素,显然空间利用率就变低了,也就是没法做到空间有效(space-efficient)。

  解决方法也简单,就是使用多个 Hash 算法,如果它们有一个说元素不在集合中,那肯定就不在,如下:

  

Hash1(URL1) = 3,Hash2(URL1) = 5,Hash3(URL1) = 6Hash1(URL2) = 5,Hash2(URL2) = 8,Hash3(URL2) = 14Hash1(URL3) = 4,Hash2(URL3) = 7,Hash3(URL3) = 10

 

  以上就是布隆过滤器做法,使用了k个哈希函数,每个字符串跟 k 个 bit 对应,从而降低了冲突的概率。

  

 

  

误判现象

上面的做法同样存在问题,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断这个值存在。比如此时来一个不存在的URL1000,经过哈希计算后,发现 bit 位为下:

 

  

Hash1(URL1000) = 7,Hash2(URL1000) = 8,Hash3(URL1000) = 14

 

  但是上面这些 bit 位已经被URL1,URL2,URL3置为 1 了,此时程序就会判断URL1000值存在。

  这就是布隆过滤器的误判现象,所以,布隆过滤器判断存在的不一定存在,但是,判断不存在的一定不存在。

  布隆过滤器可精确的代表一个集合,可精确判断某一元素是否在此集合中,精确程度由用户的具体设计决定,达到 100% 的正确是不可能的。但是布隆过滤器的优势在于,利用很少的空间可以达到较高的精确率

  

 

  

实现

 

  

Redis 的 bitmap

基于redis 的 bitmap数据结构的相关指令来执行。

 

  

 

  

RedisBloom

布隆过滤器可以使用 Redis 中的位图(bitmap)操作实现,直到 Redis4.0 版本提供了插件功能,Redis 官方提供的布隆过滤器才正式登场,布隆过滤器作为一个插件加载到 Redis Server 中,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module。

 

  详细安装、指令操作参考:https://github.com/RedisBloom/RedisBloom

  文档地址:https://oss.redislabs.com/redisbloom/

  

 

  

Guava 的 BloomFilter

Guava 项目发布版本11.0时,新添加的功能之一是BloomFilter类。

 

  

 

  

Redisson

Redisson 底层基于位图实现了一个布隆过滤器。

 

  

public static void main(String[] args) { Config config = new Config(); // 单机环境 config.useSingleServer().setAddress("redis://192.168.153.128:6379"); //构造Redisson RedissonClient redisson = Redisson.create(config); RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList"); //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的 bit 数组大小 bloomFilter.tryInit(100000L, 0.03); //将 10086 插入到布隆过滤器中 bloomFilter.add("10086"); //判断下面号码是否在布隆过滤器中 System.out.println(bloomFilter.contains("10086"));//true System.out.println(bloomFilter.contains("10010"));//false System.out.println(bloomFilter.contains("10000"));//false}

 

  

解决缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,如果从存储层查不到数据则不写入缓存层。

 

  缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。

  因此我们可以用布隆过滤器来解决,在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。

  例如:一个推荐系统有 4 亿个用户 id,每个小时算法工程师会根据每个用户之前历史行为计算出推荐数据放到存储层中,但是最新的用户由于没有历史行为,就会发生缓存穿透的行为,为此可以将所有推荐数据的用户做成布隆过滤器。如果布隆过滤器认为该用户 id 不存在,那么就不会访问存储层,在一定程度保护了存储层。

  注:布隆过滤器可能会误判,放过部分请求,当不影响整体,所以目前该方案是处理此类问题最佳方案

  

 

  

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: