布隆过滤器是什么?(布隆过滤器原理解析)

前言

布隆过滤器是一种用来检索数据是否在大集合中的高效的、空间占用少的概率型数据结构,该数据结构由哈希函数以及位数组(二进制向量)来实现的,一般而言,布隆过滤器支持add 和 isExist 操作。

过滤器使用场景

1、布隆过滤器主要用于判断给定数据是否存在大数据集中。可以用来防止缓存穿透、邮箱的垃圾邮件过滤、黑名单功能等等。利用布隆过滤器可以很有效的减少磁盘 IO 或者网络请求,因为一旦一个值必定不存在的话,我们可以不用进行后续昂贵的查询请求。
2、去重:比如爬给定网址的时候对已经爬取过的 URL 去重/新闻推送去重。

实现原理

add操作-添加元素

布隆过滤器有若干个不同的哈希函数,当我们插入一个数到布隆过滤器时,n个哈希函数会对数据值进行运算,分别映射到位数组的n个位置,此时这个位置的值都更新为1。此处图例就是通过将"线性代数"这个值插入布隆过滤器,然后通过三个不同的哈希函数将其映射到三个不同的位置,并将该位置值更新为1。
在这里插入图片描述

isExist 操作-判断元素存在与否

该操作用来判断元素是否存在于过滤器中。当我需要判断某个值是否在布隆过滤器中时,我们使用布隆过滤器的所有hash函数对该元素值进行计算,找到位数组中映射的位置,只有当位数组所有映射位置的值都为1,才能确定该值存在,否则定为不存在。例图中我们查找"高等数学"这个元素是否存在,那我们就用布隆过滤器设置的hash函数(这里设置的是三个hash函数)对元素值进行hash运算计算出位数组的下标,若三个hash函数计算出的下标的位置中的值都为1,则该元素存在(其实只是可能存在,原因下面详述)否则,该元素一定不存在。
在这里插入图片描述

布隆过滤器是概率型的原因

当过滤器中数据插入过多,而位数组相对过小,则会存在不同的数据映射的位置有交集。极致时,检索一个不存在的数据,所映射的所有位置都为1。所以说过滤器认为存在的是可能存在,因为存在误判。但认为不存在的,则一定不存在。

如何选择过滤器的长度和hash函数个数?

过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

可以使用以下公式来进行长度和hash函数个数的选择(k为函数个数,m为数组长度,n为插入的元素个数,p为误报率)
在这里插入图片描述

Redis中的布隆过滤器

redis经常发生缓存击穿问题,一般面对这种问题,除了在接口层进行校验,通常采取两种措施,一种是采用缓存空值的方法,当数据库和Redis中都不存在key,在数据库返回null时,在Redis中插入<key,null,expireTime>(缓存时间可以设置个较短的时间),当key再次请求时,Redis直接返回null,而不用再次请求数据库。另外一种就是使用布隆过滤器进行过滤。

过滤原理

将数据库中所有的key放入布隆过滤器中,当一个查询请求过来时,先经过布隆过滤器进行查询,如果判断请求查询值存在,则继续查缓存;如果判断请求查询不存在,直接丢弃,避免查询数据库。
在这里插入图片描述

Redis应用技巧

redis的bitmap只支持2^32大小,对应到内存就是512MB,数组的下标最大只能是
2^32-1。但我们的布隆过滤器很大,因此我们通过使用多个bitmap组成一个布隆过滤器。需要注意的是我们对一个元素key值进行hash运算之后应该落在同一个bitmap上。

Redis安装布隆过滤器

Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。
我们可以使用Docker来进行Redis安装,也可以直接在https://github.com/RedisBloom/RedisBloom下载最新的release源码,在编译服务器进行解压编译,得到动态库后再进行插件安装。

Redis操作过滤器常用命令

1、BF.ADD:将元素添加到布隆过滤器中,如果该过滤器尚不存在,则创建该过滤器。
格式:BF.ADD {key} {item}。
2、BF.MADD : 将一个或多个元素添加到“布隆过滤器”中,并创建一个尚不存在的过滤器。该命令的操作方式BF.ADD与之相同,只不过它允许多个输入并返回多个值。
格式:BF.MADD {key} {item} [item …] 。
3、BF.EXISTS : 确定元素是否在布隆过滤器中存在。
格式:BF.EXISTS {key} {item}。
4、BF.MEXISTS : 确定一个或者多个元素是否在布隆过滤器中存在
格式:BF.MEXISTS {key} {item} [item …]。
5、BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion]:用来创建一个布隆过滤器
key:布隆过滤器的名称
error_rate :误报的期望概率。这应该是介于0到1之间的十进制值。例如,对于期望的误报率0.1%(1000中为1),error_rate应该设置为0.001。该数字越接近零,则每个项目的内存消耗越大,并且每个操作的CPU使用率越高。
capacity: 过滤器的容量。当实际存储的元素个数超过这个值之后,性能将开始下降。实际的降级将取决于超出限制的程度。随着过滤器元素数量呈指数增长,性能将线性下降。
可选参数:
expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以expansion。默认扩展值为2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。

127.0.0.1:6379> BF.ADD myFilter java
(integer) 1
127.0.0.1:6379> BF.ADD myFilter CSDN
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter java
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter CSDN
(integer) 1
127.0.0.1:6379> BF.EXISTS myFilter XUXIANG
(integer) 0

实现布隆过滤器的其它方法

Google开源的 Guava中自带的布隆过滤器

用Google开源的 Guava中自带的布隆过滤器,但这种方法的缺点就在于只能单机使用,不能应用于分布式。
使用方法-在项目中引入 Guava 的依赖

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

实际使用示例如下

     // 创建布隆过滤器对象,大小位3600,容错率为0.01
        BloomFilter<Integer> filter = BloomFilter.create(
                Funnels.integerFunnel(),
                3600,
                0.01);
        // 判断指定元素是否存在
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));
        // 将元素添加进布隆过滤器
        filter.put(1);
        filter.put(2);
        System.out.println(filter.mightContain(1));
        System.out.println(filter.mightContain(2));

在java中使用BitSet实现布隆过滤器

1、使用位数组BitSet保存数据
2、几个不同的高效率哈希函数
3、添加元素到位数组(布隆过滤器)的方法实现
4、判断给定元素是否存在于位数组(布隆过滤器)的方法实现。

package dataStructure;


import java.util.BitSet;

/*
@function 布隆过滤器的实现
@problem description
  一个合适大小的位数组保存数据
        几个不同的哈希函数
        添加元素到位数组(布隆过滤器)的方法实现
        判断给定元素是否存在于位数组(布隆过滤器)的方法实现。
@date 20-3-9
*/

public class MyBloomFilter {
    /**
     * 位数组的大小
     */
    private static final int DEFAULT_SIZE = 2 << 24;
    /**
     * 通过这个数组可以创建 6 个不同的哈希函数
     */
    private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};

    /**
     * 位数组。数组中的元素只能是 0 或者 1,BitSet的大小为long类型大小(64位)的整数倍。非安全性,去重
     */
    private BitSet bits = new BitSet(DEFAULT_SIZE);

    /**
     * 存放包含 hash 函数的类的数组
     */
    private SimpleHash[] func = new SimpleHash[SEEDS.length];

    /**
     * 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样
     */
    public MyBloomFilter() {
        // 初始化多个不同的 Hash 函数
        for (int i = 0; i < SEEDS.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]);
        }
    }

    /**
     * 添加元素到位数组
     */
    public void add(Object value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    /**
     * 判断指定元素是否存在于位数组
     */
    public boolean contains(Object value) {
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(value));
        }
        return ret;
    }

    /**
     * 静态内部类。用于 hash 操作!
     */
    public static class SimpleHash {
        private int cap;
        private int seed;
        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        /**
         * 计算 hash 值,利用hashCode、
         */
        public int hash(Object value) {
            int h;
            return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
        }

    }
    public static void main(String[] args){
        String value1 = "https://javaguide.cn/";
        String value2 = "https://github.com/Snailclimb";
        MyBloomFilter filter = new MyBloomFilter();
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
        filter.add(value1);
        filter.add(value2);
        System.out.println(filter.contains(value1));
        System.out.println(filter.contains(value2));
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值