吊打面试官:海量数据处理利器,布隆过滤器

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 吊打面试官:海量数据处理利器,布隆过滤器

作者:小牛呼噜噜 | https://xiaoniuhululu.com计算机内功、JAVA底层、面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」

概念

通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景,一般想到的是将集合中所有元素保存起来,然后通过比较确定。链表、树、散列表(又叫哈希表,Hash table)等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间也会呈现线性增长,最终达到瓶颈。同时检索速度也越来越慢,上述三种结构的检索时间复杂度分别为O(n), O(logn), O(1)。这个时候,布隆过滤器就应运而生。布隆过滤器(Bloom Filter)是1970年由布隆提出的。布隆过滤器其实就是一个很长的二进制向量和一系列随机映射函数。可以用于快速检索一个元素是否在一个集合中出现的方法。

原理

如果想判断一个元素是不是在一个集合里,我们一般想到的是将所有元素保存起来,然后通过比较确定。我们熟悉的链表,树等等数据结构都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这其实就是布隆过滤器的基本思想。

Hash算法面临的问题就是hash冲突。假设 Hash 函数是良好的,如果我们的位阵列长度为 m 个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳 m/100 个元素。显然这就不叫空间有效了(Space-efficient)。解决方法:就是使用多个 Hash算法如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,有一定可能性它们在说谎,虽然概率比较低

算法:

  1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
  2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
  3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
  4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

其优点:

  1. 空间效率和查询时间都比一般的算法要好的多,比如增加和查询元素的时间复杂为O(N)
  2. 由于不需要存储key,所以特别节省存储空间。
  3. 保密性强,布隆过滤器不存储元素本身~~

其缺点:

  1. 由于采用hash算法,可能出现hash冲突,导致有一定的误判率,但是可以通过调整参数来降低

布隆过滤器的误判是指多个输入经过哈希之后在相同的bit位置1了,这样就无法判断究竟是哪个输入产生的,因此误判的根源在于相同的 bit 位被多次映射且置 1。

  1. 无法获取元素本身
  2. 由于hash算法导致hash冲突必然存在,所以删除元素是很困难的,而且删掉元素会导致误判率增加。

布隆过滤器的使用场景

我们可以充分利用布隆过滤器的特点:如果布隆过滤器说有一个说元素不在集合中,那肯定就不在。如果布隆过滤器说在,有一定可能性它在说谎

  1. 比较热门的场景就是:解决Redis缓存穿透问题

缓存穿透: 指用户的请求去查询缓存和数据库中都不存在的数据,可用户还是源源不断的发起请求,导致每次请求都会打到数据库上,从而压垮数据库

  1. 邮件过滤,使用布隆过滤器来做邮件黑名单过滤,还有重复推荐内容过滤,网址过滤, web请求访问拦截器,等等
  2. 许多数据库内置布隆过滤器,用于判断数据是否存在,可以减少数据库很多不必要的磁盘IO操作

简单模拟布隆过滤器

我们来看一个例子:

public class MyBloomFilter {
    /**
     * 一个长度为10 亿的比特位
     */
    private static final int DEFAULT_SIZE = 256 << 22;
    /**
     * 为了降低错误率,使用加法hash算法,所以定义一个8个元素的质数数组
     */
    private static final int[] seeds = {3, 5, 7, 11, 13, 31, 37, 61};
    /**
     * 相当于构建 8 个不同的hash算法
     */
    private static HashFunction[] functions = new HashFunction[seeds.length];
    /**
     * 初始化布隆过滤器的 bitmap
     */
    private static BitSet bitset = new BitSet(DEFAULT_SIZE);
    /**
     * 添加数据
     *
     * @param value 需要加入的值
     */
    public static void add(String value) {
        if (value != null) {
            for (HashFunction f : functions) {
                //计算 hash 值并修改 bitmap 中相应位置为 true
                bitset.set(f.hash(value), true);
            }
        }
    }
    /**
     * 判断相应元素是否存在
     * @param value 需要判断的元素
     * @return 结果
     */
    public static boolean contains(String value) {
        if (value == null) {
            return false;
        }
        boolean ret = true;
        for (HashFunction f : functions) {
            ret = bitset.get(f.hash(value));
            //一个 hash 函数返回 false 则跳出循环
            if (!ret) {
                break;
            }
        }
        return ret;
    }
    /**
     * 模拟用户在不在线。。。
     */
    public static void main(String[] args) {
        for (int i = 0; i < seeds.length; i++) {
            functions[i] = new HashFunction(DEFAULT_SIZE, seeds[i]);
        }
        // 添加1亿数据
        for (int i = 0; i < 100000000; i++) {
            add(String.valueOf(i));
        }
        String id = "123456789";
        add(id);
        System.out.println(contains(id));   //结果: true
        System.out.println("" + contains("234567890"));  //结果: false
    }
}
class HashFunction {
    private int size;
    private int seed;
    public HashFunction(int size, int seed) {
        this.size = size;
        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);
        }
        int r = (size - 1) & result;
        return (size - 1) & result;
    }
}


我们平时学习的时候可以去实现一下算法,但实际开发过程中,一般不推荐重复造轮子,简单的实现布隆过滤器, 我们一般可以用google.guava

Guava布隆过滤器

首先引入依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.0-jre</version>
</dependency>

举个例子:

// 创建布隆过滤器对象,预计包含的数据量:2000个,和允许的误差值0.01
BloomFilter<Integer> filter = BloomFilter.create(
        Funnels.integerFunnel(),
        2000,
        0.01);
System.out.println(filter.mightContain(10));// 判断指定元素是否存在
System.out.println(filter.mightContain(20));
filter.put(10);// 将元素添加进布隆过滤器
filter.put(20);
System.out.println(filter.mightContain(10));// 判断指定元素是否存在
System.out.println(filter.mightContain(20));

其中:当mightContain()方法返回_true_时,我们可以大概率确定该元素在过滤器中,但当过滤器返回_false_时,我们可以100%确定该元素不存在于过滤器中。布隆过滤器的 允许的误差值 越小,需要的存储空间就越大,对于不需要过于精确的场景,允许的误差值 设置稍大一点也可以。Guava 提供的布隆过滤器的实现还是很不错的,但是随着微服务、分布式的不断发展,对于微服务多实例的场景下就不太适用了,只适合单机,解决方案是:一般是借助Redis中的布隆过滤器

Redis布隆过滤器

Redis 4.0 的时候官方提供了插件机制,布隆过滤器正式登场。以下网站可以下载官方提供的已经编译好的可拓展模块。https://redis.com/redis-enterprise-software/download-center/modules

这边使用docker安装,自己挑选合适的镜像

~ docker pull redislabs/rebloom:latest
~ docker run -p 6379:6379 --name redis-bloom redislabs/rebloom:latest
~ docker exec -it redis-bloom bash 
root@113d012d35:/data# redis-cli
127.0.0.1:6379> 

进入容器内部后,常用的命令:

//-------------------------常用命令
BF.ADD --添加一个元素到布隆过滤器
BF.EXISTS --判断元素是否在布隆过滤器
BF.MADD --添加多个元素到布隆过滤器
BF.MEXISTS --判断多个元素是否在布隆过滤器
//-------------------------具体操作
127.0.0.1:6379> BF.ADD myFilter hello
(integer) 1
127.0.0.1:6379> BF.ADD myFilter people
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter hello
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter people
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter github
(integer) 0

布谷鸟过滤器

为了解决布隆过滤器不能删除元素的问题,布谷鸟过滤器应运而生。论文《Cuckoo Filter:Better Than Bloom》作者将布谷鸟过滤器和布隆过滤器进行了深入的对比。但是其删除并不完美,存在误删的概率,还存在插入复杂度比较高等问题。由于使用较少,本文就不过多介绍了,感兴趣的自行了解文章

参考资料:https://www.cnblogs.com/feily/articles/14048396.htmlhttps://www.cnblogs.com/liyulong1982/p/6013002.html


本篇文章到这里就结束啦,如果我的文章对你有所帮助,还请帮忙一键三连:点赞、关注、收藏,你的支持会激励我输出更高质量的文章,感谢!

计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」,我们下期再见!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
NoSQL Java Redis
面试01-Redis 如何从海量数据中查询出某一个 Key
面试01-Redis 如何从海量数据中查询出某一个 Key
131 0
|
13天前
|
存储 缓存 NoSQL
京东面试:亿级黑名单 如何设计?亿级查重 呢?(答案含:布隆过滤器、布谷鸟过滤器)
尼恩,40岁的老架构师,近期在读者交流群中分享了几个大厂面试题及其解决方案。这些问题包括亿级数据查重、黑名单存储、电话号码判断、安全网址判断等。尼恩给出了三种解决方案:使用BitMap位图、BloomFilter布隆过滤器和CuckooFilter布谷鸟过滤器。这些方法不仅高效,还能显著提升面试表现。尼恩还建议大家系统化学习,刷题《尼恩Java面试宝典PDF》,并提供简历修改和面试辅导,帮助大家实现“offer自由”。更多技术资料和PDF可在公众号【技术自由圈】获取。
|
2月前
|
存储 NoSQL Java
面试官:项目中如何实现布隆过滤器?
面试官:项目中如何实现布隆过滤器?
41 0
面试官:项目中如何实现布隆过滤器?
|
缓存 关系型数据库 MySQL
MySQL海量数据优化(理论+实战) 吊打面试官
一、准备表数据 咱们建一张用户表,表中的字段有用户ID、用户名、地址、记录创建时间,如图所示
145 0
|
6月前
|
算法
【数据结构】盘点那些经典的 [哈希面试题]【哈希切割】【位图应用】【布隆过滤器】(10)
【数据结构】盘点那些经典的 [哈希面试题]【哈希切割】【位图应用】【布隆过滤器】(10)
|
存储 缓存 NoSQL
缓存面试解析:穿透、击穿、雪崩,一致性、分布式锁、Redis过期,海量数据查找
本文提供了一些保证数据一致性和设计分布式锁的策略。这些策略可以在实际应用中帮助开发人员解决相关的问题,确保系统的数据一致性和并发访问的正确性。同时,通过合理地使用缓存和分布式锁,可以提高系统的性能和可靠性。希望对你在面对Redis相关面试题时有所帮助!
423 0
|
算法 数据处理 C++
【位图&&布隆过滤器&&海量数据面试题】(一)
【位图&&布隆过滤器&&海量数据面试题】(一)
101 0
【位图&&布隆过滤器&&海量数据面试题】(一)
|
存储 SQL 算法
【位图&&布隆过滤器&&海量数据面试题】(二)
【位图&&布隆过滤器&&海量数据面试题】(二)
90 0
|
SQL 分布式计算 大数据
大数据面试题:Hive count(distinct)有几个reduce,海量数据会有什么问题
count(distinct)只有1个reduce。 为什么只有一个reducer呢,因为使用了distinct和count(full aggreates),这两个函数产生的mr作业只会产生一个reducer,而且哪怕显式指定set mapred.reduce.tasks=100000也是没用的。 当使用count(distinct)处理海量数据(比如达到一亿以上)时,会使得运行速度变得很慢,熟悉mr原理的就明白这时sql跑的慢的原因,因为出现了很严重的数据倾斜。
|
存储 NoSQL 搜索推荐
海量数据被面试官逼问,就那几种死记硬背下来,没多大技术含量,建议面试前准备下
海量数据被面试官逼问,就那几种死记硬背下来,没多大技术含量,建议面试前准备下