布隆过滤器-BloomFilter

本文详细介绍了布隆过滤器的工作原理,包括其在内存中声明Bit数组、通过多次hash确定元素位置的过程,以及如何判断元素可能存在于列表中。同时,文章探讨了布隆过滤器的特点,如存在误报但不会漏报,以及无法删除元素等问题,并讨论了如何通过增加hash次数和bit数组长度来提高准确性。最后,提到了布隆过滤器在防止缓存穿透、去重和幂等处理等场景的应用。

目录

 

一、概述

二、详解

三、实现

四、适用业务场景


一、概述

简单讲布隆过滤器就是判断一个列表中是否存在某个元素。一般在JAVA判断是否存在,我们可以Map,Set等容器。但是当数据量特别大的时候,用Map和Set会占用过多的内存。这个时候就会考虑用布隆过滤器了。

二、详解

要创建一个布隆过滤器首选需要在内存中声明一个Bit数组,假设数组的长度为L,初始值全部为0。

       当put一个key到布隆过滤器的时候,会对key进行N次hash,然后对hash值 % L 取模,得到N个位置下标。然后将Bit数组中对应位置的值全部设置为1。其中的L和N取决于key的预估总数和错误率,因为bloomfilter不能保证100%的准确,这个后面会说。

       当判断一个key是否存在的时候也是对key进行N次hash取模,如果所有bit数组中所有位置的值都为1,则认为这个key有可能存在,注意这里说是有可能。

过程如下图:(这里假设L为10, N为3)

假设:

"zhangsan":3次hash取模的结果为:0,2,4。

"lisi":3次hash取模的结果为:4,6,8。

"wangwu":3次hash取模的结果为:2,4,6。

如果已经存在"zhangsan"和"lisi"这俩个key,那么即使"wangwu"这个key实际不存在,但是算法返回的结果是存在,因为2,4,6这个三个位置已经被“zhangsan”和“lisi”占用了。

综上所述发现bloomfiter有一些特点:

  1. 如果算法返回不存在,那刻个key肯定不存在。
  2. 如果算法返回存在,那只能说明有可能存在。
  3. bloomfiter中的key无法删除。因为bit位是复用的,删除会影响别的key。

那么怎么提升算法的准确度呢?

  1. 增加hash的次数(CPU和准确度的取舍)
  2. 增加bit数组的长度(内存和准确度的取舍)

三、实现

  1. 自己写java代码实现
    package com.ikuboo.bloomfilter;
    
    import java.util.BitSet;
    
    /**
     * 布隆过滤器
     */
    public class MyBloomFilter {
    
        private int length;
    
        /**
         * bitset
         */
        private BitSet bitSet;
    
        public MyBloomFilter(int length) {
            this.length = length;
            this.bitSet = new BitSet(length);
        }
    
        /**
         * 写入数据
         */
        public void put(String key) {
            int first = hashcode_1(key);
            int second = hashcode_2(key);
            int third = hashcode_3(key);
    
            bitSet.set(first % length);
            bitSet.set(second % length);
            bitSet.set(third % length);
        }
    
        /**
         * 判断数据是否存在
         *
         * @param key
         * @return true:存在,false:不存在
         */
        public boolean exist(String key) {
            int first = hashcode_1(key);
            int second = hashcode_2(key);
            int third = hashcode_3(key);
    
            boolean firstIndex = bitSet.get(first % length);
            if (!firstIndex) {
                return false;
            }
            boolean secondIndex = bitSet.get(second % length);
            if (!secondIndex) {
                return false;
            }
            boolean thirdIndex = bitSet.get(third % length);
            if (!thirdIndex) {
                return false;
            }
            return true;
        }
    
        /**
         * hash 算法1
         */
        private int hashcode_1(String key) {
            int hash = 0;
            int i;
            for (i = 0; i < key.length(); ++i) {
                hash = 33 * hash + key.charAt(i);
            }
            return Math.abs(hash);
        }
    
        /**
         * hash 算法2
         */
        private int hashcode_2(String data) {
            final int p = 16777619;
            int hash = (int) 2166136261L;
            for (int i = 0; i < data.length(); i++) {
                hash = (hash ^ data.charAt(i)) * p;
            }
            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            hash += hash << 5;
            return Math.abs(hash);
        }
    
        /**
         * hash 算法3
         */
        private int hashcode_3(String key) {
            int hash, i;
            for (hash = 0, i = 0; i < key.length(); ++i) {
                hash += key.charAt(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            hash += (hash << 3);
            hash ^= (hash >> 11);
            hash += (hash << 15);
            return Math.abs(hash);
        }
    
    }
    

    测试代码

    public class TestMyBloomFilter {
        public static void main(String[] args) {
            int capacity = 10000000;
            MyBloomFilter bloomFilters = new MyBloomFilter(capacity);
            bloomFilters.put("key1");
    
            System.out.println("key1是否存在:" + bloomFilters.exist("key1"));
            System.out.println("key2是否存在:" + bloomFilters.exist("key2"));
        }
    }

     

  2. guava类库实现
    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    
    import java.nio.charset.Charset;
    
    public class TestGuavaBloomFilter {
        public static void main(String[] args) {
            //预估的容量
            int capacity = 10000000;
            //期望的错误率
            double fpp = 0.01;
    
            BloomFilter<String> bloomFilters = BloomFilter.create(
                    Funnels.stringFunnel(Charset.forName("UTF-8")), capacity, fpp);
    
            bloomFilters.put("key1");
    
            System.out.println("key1是否存在:" + bloomFilters.mightContain("key1"));
            System.out.println("key2是否存在:" + bloomFilters.mightContain("key2"));
        }
    }

     

  3. 配合redis实现
    1. 可以自己写代码利用redis bitmap数据结构显示。可以参考guava里面的计算hash次数和hash的想相关代码,就guava里的BitArray替换为redis的bitmap即可。
    2. 可以用lua脚本实现。参考:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
    3. 利用redis 4.0+提供的插件功能实现。参考:https://blog.youkuaiyun.com/u013030276/article/details/88350641

四、适用业务场景

  1. 防止缓存穿透
  2. 去重,幂等处理等流程

 

 

 

 

### 布隆过滤器在解决缓存穿透问题中的应用 布隆过滤器(Bloom Filter)作为一种高效的空间节省型数据结构,被广泛应用于各种场景下的快速成员检测。它特别适合用来处理大规模数据集的查询操作,尤其是在需要判断某个元素是否属于某一集合的情况下。以下是关于如何利用布隆过滤器防止缓存穿透的具体方案: #### 工作原理概述 布隆过滤器的核心是由一个长度固定的比特数组以及多个独立的哈希函数组成[^1]。当向其中插入新元素时,这些哈希函数会分别计算出对应的位置索引并将相应位置上的值置为1;而在查找阶段,则只需验证所有指定位置是否均为1即可得出结论——如果存在任何一个零值,则表明目标对象肯定不属于当前集合。 然而值得注意的是,由于可能存在不同元素经过各自不同的哈希运算后指向完全相同的几个位的情况发生冲突从而造成假阳性结果的现象,即对于那些实际上并不存在于原始集合内的项目也可能被判别成“可能包含”。尽管如此,只要合理控制参数配置如预期最大容量N与允许误差率p之间关系等因素的影响程度,就可以使得最终产生的误判几率保持在一个较低水平范围内接受范围之内[^2]。 #### Java 实现案例分析 下面给出一段基于 Google Guava 库实现的一个简单例子展示怎样构建这样一个用于预防缓存击穿现象出现的基础框架结构: ```java // 导入必要的类库 import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class CachePenetrationPrevention { // 定义静态变量保存全局唯一实例化的布隆过滤器对象 private static final BloomFilter<String> BLOOM_FILTER; static{ // 构造方法调用前先完成初始化工作 int expectedInsertions = 100000; // 预估最多容纳多少条记录 double fpp = 0.01d; // 可容忍的最大错误概率百分比 // 调用工厂模式创建具体的布隆过滤器实体 BLOOM_FILTER = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), expectedInsertions, fpp); // 将一些已知有效的key预先加载进去形成基础底座 preloadKnownKeys(); } /** * 模拟预填充部分常见合法请求路径作为白名单参照物 */ private static void preloadKnownKeys(){ String[] knownValidRequests={"itemA","productB","serviceC"}; for(String key : knownValidRequests){ putIntoBloomFilter(key); } } /** * 把给定字符串形式的关键字加入到布隆过滤器当中去 * @param key 待录入的新关键字 */ public static void putIntoBloomFilter(String key){ if(!mightContainInBloomFilter(key)){ BLOOM_FILTER.put(key); } } /** * 测试传进来待测关键词是否有可能已经收录过来了呢? * @param key 输入测试的目标关键字 * @return 返回布尔类型的判定答案 */ public static boolean mightContainInBloomFilter(String key){ return BLOOM_FILTER.mightContain(key); } } ``` 在这个示例代码片段里展示了完整的生命周期管理流程图谱:从最开始定义好固定规格大小直至最后提供对外公开接口供其他模块调用为止整个过程一览无余。通过这种方式不仅可以有效减少不必要的数据库负载压力而且还能大幅提升整体响应速度体验效果明显优于传统单一层次防护措施单独作战所能达到的高度。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值