美文网首页redis&mongo&zookeeper
海量数据下的去重和查重(二):布隆过滤器

海量数据下的去重和查重(二):布隆过滤器

作者: 雪飘千里 | 来源:发表于2020-03-15 22:46 被阅读0次

    在上篇文章海量数据下的去重和查重(一):BitMap位图法的最后,我们说到位图法缺点,是其所占空间随集合内最大元素的增大而增大。这就会带来一个问题,如果查找的元素数量少但其中某个元素的值很大,比如数字范围是 1 到 1000 亿,那消耗的空间不容乐观。

    针对这个缺点,有一种改进是布隆过滤器。

    布隆过滤器

    所谓布隆过滤器,实际上就是一个位图bitMap,我们现在假设有一个长度为m的bit类型的数组,以及 K 个互相独立的优秀的哈希函数,且这k个哈希函数的输出域都大于或等于 m。

    当一个元素加入布隆过滤器中的时候,会进行如下操作:

    • 使用 K 个哈希函数对元素值进行 K 次计算,得到 K 个哈希值,我们将这K个值对m模运算,得到的 k 个值都在 0~m之间
    • 根据得到的哈希值,在位数组中把对应下标的值置为 1。

    当要判断一个值是否在布隆过滤器中,对元素再次进行哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

    image.png

    布隆过滤器最大的用处,就是可以用来判断一个元素是否在一个集合中,很常用的一个功能是用来去重。比如可以用来解决下面的需求

    需求一:不安全网页的黑名单包含100亿个黑名单网页,每个网页的URL最多占用64B,现在要实现一个网页过滤系统,根据网页的URL判断该网页是否在黑名单上。我们可以把黑名单url加载到数据库或者redis中,但是数据库存在效率问题,redis存在内存问题。

    需求二:在爬虫中,目标网站 URL 千千万,怎么判断某个 URL 爬虫是否宠幸过?简单点可以爬虫每采集过一个 URL,就把这个 URL 存入数据库中,每次一个新的 URL 过来就到数据库查询下是否访问过。
    但是随着爬虫爬过的 URL 越来越多,每次请求前都要访问数据库一次,并且对于这种字符串的 SQL 查询效率并不高。除了数据库之外,使用 Redis 的 set 结构也可以满足这个需求,并且性能优于数据库。而Redis 也存在一个问题:耗费过多的内存。

    对于上述两个问题,相比于数据库和 Redis,使用布隆过滤器可以很好的避免性能和内存占用的问题。

    特点——宁可错杀三千,绝不放过一个

    从上面的介绍中可以看出,当插入的元素越来越多,位数组中被置为 1 的位置就越多,当一个不在布隆过滤器中的元素,经过哈希计算之后,得到的值在位数组中查询,有可能这些位置也都被置为 1。这样一个不存在布隆过滤器中的也有可能被误判成在布隆过滤器中。但是如果布隆过滤器判断说一个元素不在布隆过滤器中,那么这个值就一定不在布隆过滤器中。简单来说:

    布隆过滤器说某个元素在,可能会被误判。
    布隆过滤器说某个元素不在,那么一定不在。

    这个布隆过滤器的缺陷放到上面爬虫的需求中,可能存在某些没有访问过的 URL 可能会被误判为访问过,但是如果是访问过的 URL 一定不会被误判为没访问过。在黑名单需求中,一个正常url可能会被误判为是黑名单url,从而被拦截,但是一个黑名单url是肯定会被拦截的。

    虽然存在这个问题,但是在这两种需求中,相对于性能和内存来说,一点点的误判率是可以接受的。

    常见的补救办法是建立白名单,存储那些可能被误判的元素。 比如你苦等的offer 可能被系统丢在邮件垃圾箱(白名单)了。

    使用场景

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

    • 网页爬虫对 URL 的去重,避免爬取相同的 URL 地址
    • 进行垃圾邮件过滤:反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱(同理,垃圾短信)
    • 有的黑客为了让服务宕机,他们会构建大量不存在于缓存中的 key 向服务器发起请求,在数据量足够大的情况下,频繁的数据库查询可能导致 DB 挂掉。布隆过滤器很好的解决了缓存击穿的问题,通过布隆过滤器
      将所有的可用的key放入缓存,当请求进来时,先去过滤器中校验是否存在,如果不存在直接返回null。

    项目中使用

    1、redis布隆过滤器

    <dependency>
            <groupId>com.redislabs</groupId>
            <artifactId>jrebloom</artifactId>
            <version>1.0.2</version>
    </dependency>
    
    public class RedisBloomDemo {
        public static void main(String[] args) {
            String userIdBloomKey = "userid";
            // 创建客户端,jedis实例
            Client client = new Client("localhost", 6378);
            // 创建一个有初始值和出错率的过滤器
            client.createFilter(userIdBloomKey,100000,0.01);
            // 新增一个<key,value>
            boolean userid1 = client.add(userIdBloomKey,"101310222");
            System.out.println("userid1 add " + userid1);
    
            // 批量新增values
            boolean[] booleans = client.addMulti(userIdBloomKey, "101310111", "101310222", "101310222");
            System.out.println("add multi result " + booleans);
    
            // 某个value是否存在
            boolean exists = client.exists(userIdBloomKey, "101310111");
            System.out.println("101310111 是否存在" + exists);
    
            //某批value是否存在
            boolean existsBoolean[] = client.existsMulti(userIdBloomKey, "101310111","101310222", "101310222","11111111");
            System.out.println("某批value是否存在 " + existsBoolean);
        }
    }
    
    

    2、Guava中的BloomFilter

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>22.0</version>
    </dependency>
    
    
    private static int size = 1000000;
    private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), size, 0.0001);
    
    public void test2() {
        String aa = "zjl";
        bloomFilter.put(aa);
        System.out.println(bloomFilter.mightContain(aa));
    }
    
    

    相关文章

      网友评论

        本文标题:海量数据下的去重和查重(二):布隆过滤器

        本文链接:https://www.haomeiwen.com/subject/dlrvehtx.html