布隆过滤器的实现应用

概念

布隆过滤器主要用于缓存穿透,一般我们会把数据放在Redis里缓存,请求过来先读缓存,有缓存的话直接返回,如果缓存中没有,再去访问数据库查询,然后再把数据库读取的数据放入缓存。但是如果有大量请求过来,而且都在访问一个不在缓存里的数据,这时这些大量请求都会到数据库,对数据库造成很大压力。

可以用很多办法来解决这个问题,如分布式锁、布隆过滤器。布隆过滤器可以缓解缓存穿透问题,为什么说是缓解,而不是解决呢?这是因为布隆过滤器会有一定的误判率。

布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0。

在这里插入图片描述

现在添加一个数据key1,这个数据会通过多个不同的哈希函数计算,并将结果位置上对应位的值置为 “1”,比如Hash1(key1)=5,Hash2(key1)=9,Hash3(key1)=2

在这里插入图片描述

这时数据key1就占据了bitmap的2、5、9三个位置,可以看出,布隆过滤器没有存放完整的数据,只是运用一系列随机映射函数计算出位置,然后填充二进制向量。

在这里插入图片描述

如果一个数据经过多个映射函数,结果都是1,不一定代表这个数据一定存在,也许其他数据经过映射函数计算的结果也是相同的,也就是说布隆过滤器只能判断数据是否一定不存在,而无法判断数据是否一定存在。布隆过滤器是不能删除数据的,因为他要删除的数据所在bitmap的位置,其他数据有可能也在使用。

  • 优点:由于存放的不是完整的数据,所以占用的内存很少,而且新增,查询速度够快;
  • 缺点: 随着数据的增加,误判率随之增加;无法做到删除数据;只能判断数据是否一定不存在,而无法判断数据是否一定存在。

实现

布隆过滤器实现有三种:

  1. client实现bloom算法和bitmap+redis
  2. client实现bloom算法+redis bitmap
  3. client+redis bloom.so bitmap

guava实现布隆过滤器

google guava帮助我们实现了如何设计随即映射函数,首先引入pom

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.0</version>
</dependency>

代码实现

public class Application {

    private static int size=1000000;//插入数据

    private static double fpp=0.01;//期望的误判率

    private static BloomFilter<Integer> bloomFilter=BloomFilter.create(Funnels.integerFunnel(),size,fpp);

    public static void main(String[] args) {
        //插入数据
        for (int i = 0; i < 1000000; i++) {
            bloomFilter.put(i);
        }
        int count=0;

        for (int i = 1000000; i <2000000 ; i++) {
            if (bloomFilter.mightContain(i)){
                count++;
                System.out.println(i+"误判了");
            }
        }
        System.out.println("总共的误判书:"+count);
    }
}

我们定义了一个布隆过滤器,有两个重要的参数,分别是 我们预计要插入多少数据,我们所期望的误判率,误判率不能为0。

向布隆过滤器插入了0-1000000,然后用1000000-2000000来测试误判率。运行结果:

1999417误判了
1999419误判了
1999501误判了
1999567误判了
1999640误判了
1999697误判了
1999827误判了
1999942误判了
总共的误判书:10314

正常是有100万的数据是不存在的,误判了10314次。误判率是0.010314,与所定义的期望误判率0.01接近

当把误判率改成0.03,运行结果:

1999789误判了
1999822误判了
1999882误判了
1999914误判了
1999956误判了
1999962误判了
1999996误判了
总共的误判书:30155

误判率 fpp 的值越小,匹配的精度越高。当减少误判率 fpp 的值,需要的存储空间也越大,所以在实际使用过程中需要在误判率和存储空间之间做个权衡。

Redis Modules RedisBloom

RedisBloom自己实现映射函数和bitmap

redis.io官网Modules找到RedisBloom,github地址

下载解压编译

 unzip master.zip
 cd RedisBloom-master/
 make

使用redis-server时参数加载 使用绝对路径,也可以配置再配置文件中

redis-server  --loadmodule /home/software/RedisBloom-master/redisbloom.so 

连接到客户端redis-cli

#BF.RESERVE {key} {error_rate} {capacity} 创建一个空的BF,带着允许的误差率和容量
127.0.0.1:6382> BF.RESERVE k1 0.01 1000000
OK
# 向key的BF里标记存在lisi
127.0.0.1:6382> BF.ADD k1 lisi
(integer) 1
#看lisi是否存在于BF中,1表示存在,0表示不存在
127.0.0.1:6382> BF.EXISTS k1 lisi
(integer) 1
127.0.0.1:6382> BF.EXISTS k1 lisi1
(integer) 0
#BF.INSERT 插入,可以插入多条,选择一些参数,可以认为是RESERVE和ADD的结合体

客户端设计映射函数和bitmap实现布隆过滤器

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.4.2</version>
    </dependency>
public class RedisMain {

    static final int expectedInsertions = 100;//要插入多少数据
    static final double fpp = 0.01;//期望的误判率

    //bit数组长度
    private static long numBits;

    //hash函数数量
    private static int numHashFunctions;

    static {
        numBits = optimalNumOfBits(expectedInsertions, fpp);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.234.128", 6379);
        for (int i = 0; i < 100; i++) {
            long[] indexs = getIndexs(String.valueOf(i));
            for (long index : indexs) {
                jedis.setbit("codebear:bloom", index, true);
            }
        }
        for (int i = 100; i < 200; i++) {
            long[] indexs = getIndexs(String.valueOf(i));
            for (long index : indexs) {
                Boolean isContain = jedis.getbit("redis:bloom", index);
                if (!isContain) {
                    System.out.println(i + "肯定没有重复");
                }
            }
            System.out.println(i + "可能重复");
        }
    }

    /**
     * 根据key获取bitmap下标
     */
    private static long[] getIndexs(String key) {
        long hash1 = hash(key);
        long hash2 = hash1 >>> 16;
        long[] result = new long[numHashFunctions];
        for (int i = 0; i < numHashFunctions; i++) {
            long combinedHash = hash1 + i * hash2;
            if (combinedHash < 0) {
                combinedHash = ~combinedHash;
            }
            result[i] = combinedHash % numBits;
        }
        return result;
    }

    private static long hash(String key) {
        Charset charset = Charset.forName("UTF-8");
        return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
    }

    //计算hash函数个数
    private static int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }

    //计算bit数组长度
    private static long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }
}

运行结果

...
198肯定没有重复
198肯定没有重复
198肯定没有重复
198肯定没有重复
198可能重复
199肯定没有重复
199肯定没有重复
199肯定没有重复
199肯定没有重复
199肯定没有重复
199肯定没有重复
199肯定没有重复
199可能重复

布隆过滤器的使用场景

  • 过滤大量的数据,比如垃圾邮件过滤,钓鱼网址过滤,判断文章是否给用户推送、判断用户是否已经登陆过等。

  • 利用set的不可重复性,会造成很大的空间浪费,布隆过滤器占用空间小,在设置的时候需要有精确度。精确度越高,所需空间越大。但相对来说,节省空间,查重性能高。由于使用的是多个无偏哈希函数,可能会发生不同的元素哈希值一致,故容易误判。若业务可接受误判,可采用布隆过滤器

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值