面试题:海量数据处理利器-布隆过滤器

作者:小牛呼噜噜 | 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布隆过滤器

首先引入依赖:


    com.google.guava
    guava
    28.0-jre

举个例子:

// &#x521B;&#x5EFA;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;&#x5BF9;&#x8C61;&#xFF0C;&#x9884;&#x8BA1;&#x5305;&#x542B;&#x7684;&#x6570;&#x636E;&#x91CF;&#xFF1A;2000&#x4E2A;&#xFF0C;&#x548C;&#x5141;&#x8BB8;&#x7684;&#x8BEF;&#x5DEE;&#x503C;0.01
BloomFilter<integer> filter = BloomFilter.create(
        Funnels.integerFunnel(),
        2000,
        0.01);

System.out.println(filter.mightContain(10));// &#x5224;&#x65AD;&#x6307;&#x5B9A;&#x5143;&#x7D20;&#x662F;&#x5426;&#x5B58;&#x5728;
System.out.println(filter.mightContain(20));
filter.put(10);// &#x5C06;&#x5143;&#x7D20;&#x6DFB;&#x52A0;&#x8FDB;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;
filter.put(20);
System.out.println(filter.mightContain(10));// &#x5224;&#x65AD;&#x6307;&#x5B9A;&#x5143;&#x7D20;&#x662F;&#x5426;&#x5B58;&#x5728;
System.out.println(filter.mightContain(20));
</integer>

其中:当 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>

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

//-------------------------&#x5E38;&#x7528;&#x547D;&#x4EE4;
BF.ADD --&#x6DFB;&#x52A0;&#x4E00;&#x4E2A;&#x5143;&#x7D20;&#x5230;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;
BF.EXISTS --&#x5224;&#x65AD;&#x5143;&#x7D20;&#x662F;&#x5426;&#x5728;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;
BF.MADD --&#x6DFB;&#x52A0;&#x591A;&#x4E2A;&#x5143;&#x7D20;&#x5230;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;
BF.MEXISTS --&#x5224;&#x65AD;&#x591A;&#x4E2A;&#x5143;&#x7D20;&#x662F;&#x5426;&#x5728;&#x5E03;&#x9686;&#x8FC7;&#x6EE4;&#x5668;

//-------------------------&#x5177;&#x4F53;&#x64CD;&#x4F5C;

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.html
https://www.cnblogs.com/liyulong1982/p/6013002.html

本篇文章到这里就结束啦,很感谢你能看到最后,如果觉得文章对你有帮助,别忘记关注我!更多精彩的文章

面试题:海量数据处理利器-布隆过滤器

Original: https://www.cnblogs.com/xiaoniuhululu/p/16736861.html
Author: 小牛呼噜噜
Title: 面试题:海量数据处理利器-布隆过滤器

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/577846/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 高等代数:3 线性方程组的解集的结构

    3 线性方程组的解集的结构 1、定义1:数域K上所有n元有序数组组成的集合(K^{n}),连同定义在它上面的加法运算和数量乘法运算,以及满足的8条运算法则一起,称为数域K上的一个 …

    Linux 2023年6月8日
    085
  • 【Python】**kwargs和takes 1 positional argument but 2 were given

    Python的函数定义中可以在参数里添加kwargs——简单来说目的是允许 添加不定参数名称的参数,并作为 字典传递参数。但前提是——你必须提供 参数名**。 例如下述情况: 1 …

    Linux 2023年6月13日
    0105
  • 04_Linux基础-.&..-cat-tac-重定向-EOF-Shell-more-ps-less-head-tail-sed-grep-which-whereis-PATH-bash-usr-locate-find

    04_Linux基础-.&..-cat-tac->&>>-EOF-Shell-more-ps-less-head-tail-sed-grep-wh…

    Linux 2023年6月6日
    097
  • 搭建部署Docker

    Docker安装准备: 首先看下服务器是否有旧版本,如果有需要卸载并且安装依赖 然后下载docker仓库repo源: 安装完成后查看docker仓库版本信息: yum安装docke…

    Linux 2023年6月8日
    098
  • 嵌入式软件开发之程序架构设计-任务调度

    1 前言 在嵌入式MCU软件开发过程中,程序任务调度架构的搭建尤为重要,直接关系到该程序能支持多少功能(随着功能越多系统响应能力越弱,好的任务调度架构能够在保持相同的系统响应能力前…

    Linux 2023年6月7日
    0109
  • 数据结构 栈与队列

    cpp;gutter:true;</p> <h1>include</h1> <h1>include</h1> <h…

    Linux 2023年6月13日
    085
  • 操作系统实现-printk

    博客网址:www.shicoder.top微信:18223081347欢迎加群聊天 :452380935 这一次我们来实现最基础,也是最常见的函数 print,大家都知道这个是可变…

    Linux 2023年6月13日
    0102
  • Spring事务(二)-事务传播行为

    在Spring里,一个事务方法被另外一个事务方法调用时,两个方法的事务应该如何进行,说白话一点,就是说当出现异常需要回滚时,各个方法的数据操作是否要全部回滚,事务传播行为就是决定了…

    Linux 2023年6月6日
    071
  • Ubuntu2004使用dnsmasq+Clash+Iptables+Ipset

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月13日
    082
  • 什么是草台班子?

    有个朋友最近想跳槽,他对管理的兴趣不大,而且认为自己的性格也不适合做管理,更想成为技术专家。基于这些考虑,他希望能进入知名大厂,如果面试不顺利,去小而美公司也行。他的面试经验不多,…

    Linux 2023年6月6日
    0100
  • jmeter 安装与环境变量配置

    安装jmeter首先要安装与jmeter版本兼容的JDK,安装完成JDK后才能安装jmeter,JDK可以自行在官网下载或者通过360软件管家进行下载。 1、下载安装JDK 安装完…

    Linux 2023年6月8日
    088
  • 有道词典翻译功能数字有时无法翻译出来解决方案

    阅文时长 | 0.03分钟字数统计 | 62.4字符主要内容 | 1、引言&背景 2、解决方案 3、声明与参考资料『有道词典翻译功能数字有时无法翻译出来解决方案』 编写人 …

    Linux 2023年6月14日
    0185
  • Linux目录操作(pwd、cd、ls、alias、du、mkdir、touch)

    pwd(打印当前目录) cd(### 切换目录) 命令 效果 cd 或 cd ~ 若不指定目标位置,切换到当前用户的宿主目录(家目录) cd – 到前一次目录 一个点号…

    Linux 2023年6月6日
    081
  • linux配置密钥登录

    一、前言: ssh远程登录密码认证的方式有三种,password、Keyboard Interactive、Public Key前面两种方式就是密码认证,含义都是一样大同小异。第三…

    Linux 2023年6月8日
    078
  • ACL和NAT

    NAT 概述: NAT(网络地址翻译)一个数据包目的ip或者源ip为私网地址, 运营商的设备 无法转发数据。 NAT工作机制: 一个数据包从企业内网去往公网时,路由器将数据包当 中…

    Linux 2023年6月6日
    094
  • source insight4.0最常用到的设置

    1、常用功能 1.1:全局查找 1.2:当前文件查找 1.3:高亮设置 1.4:配置字体以及其他 1.5:配置自动缩进 1.6:其他 1. 常用功能 全局查找 Ctl+/ 查找到的…

    Linux 2023年5月27日
    0123
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球