【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器

问题

在 缓存穿透 一文中我们讲到了可以使用布隆过滤器可以解决缓存穿透问题。例如查询一个用户ID,它是如何快速查询,它又不是数组,又是怎么提高随机访问特性的查询效率的呢?
我们平时刷短视频(抖音,快手等)是如何做到刷到的视频是不重复的呢?如果把用户刷到的信息都存放在数据库中,抖音八亿用户,每次都要进行 exists 判断该视频是否被看过,滑动一下就需要去库中查看,磁盘是远远跟不上性能的。如果用缓存,缓存容量有限,且需要对浏览的记录设置永不过期,这么多用户,用内存存记录,成本的?哪个企业会这么做?
【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器_第1张图片

什么是布隆过滤器

布隆过滤器(Bloom Filter) 是 1970 年由布隆提出的。实际上是一个很长的二进制向量和若干个hash 函数,它类似于一个简单的 set 结构,但是存在一定的误判率。可以用更小的空间,解决去重问题的一种数据结构。

布隆过滤器存在一定误判率,当它说某个值不存在是,那就是一定不存在,说某一个值存在是,这个值可能不存在。只要设置参数合理,精确度足够精确,误判率极低。

使用场景

  • 去重:用最小的空间解决去重问题。
  • 解决缓存穿透:用户传不存在参数查询数据,缓存中一定没有,此时大量请求访问数据库,数据库被过跨,可以在访问数据库之前加布隆过滤器解决这些问题。
  • 垃圾邮件的过滤。
  • 文件处理软件(word),错误单词检测
  • 网络爬虫,重复的 url 检测。
  • Hbase 行过滤。

原理分析

布隆过滤器实际上是一个很长的二进制向量(仅包含 0 或 1 位值的列表,最初所有的值均设置为 0)和一系列随机映射函数。位数组,以bit为单位,相当于 int 类型的 1/32。
【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器_第2张图片
添加数据时,多个 hash 函数对 key 进行计算,得到不同的位置,并把这几个位置置位1,当查看key是否存在时,多个hash函数对key进行运算,如果对应的多个位置,其中有一个位置为0,则一定不存在,如果全部为1,不一定存在,因为存多个key时,可能会映射到相同的位置(例如上图中的 d)。可见如果布隆过滤器初始化值越大,误判率就会越低。

【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器_第3张图片
m/n 与误差率成反比,k与误差率成反比。

注意

  • 实际元素数量远小于初始化数量;
  • 当实际元素数量超过初始化数量时,重建一个更大的布隆过滤器, 再将所有的历史元素批量添加。
  • 哈希函数越多,布隆过滤器的错误率就会变大。控制布隆过滤器中哈希函数的个数。有这样一个计算最优哈希函数个数的数学公式: 哈希函数个数 k = (m/n) * ln(2)。其中 m 为 bit 数组长度,n 为要存入的对象的个数。实际上,如果哈希函数个数为 1,且数组长度足够,布隆过滤器就可以退化成一个位图。所以,我们可以认为“位图是只有一个特殊的哈希函数,且没有被压缩长度的布隆过滤器”

基本用法

bf.add 添加一个元素
bf.exists 查询一个元素是否存在
bf.madd 批量添加多个元素
bf.mexists 查询多个元素是否存在

直接在客户端操作是不可以用的,Redis 官方 提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中。

下载,解压,编译

wget https://github.com/RedisLabsModules/rebloom/archive/v1.1.1.tar.gz
tar -zxvf v1.1.1.tar.gz
cd redisbloom-1.1.1/
make

找到redis.conf 配置文件,编辑 vi redis.conf ,添加插件路径

loadmodule /usr/local/software/redisbloom-1.1.1/rebloom.so

启动

redis-server redis.conf

客户端测试,我们第一次添加数据时候,Bloom Filter 自动创建,默认的 error_rate 是 0.01,默认的 initial_size 是 100

127.0.0.1:6379> bf.add bookname java
(integer) 1
127.0.0.1:6379> bf.add bookname python
(integer) 1
127.0.0.1:6379> bf.add bookname c#
(integer) 1
127.0.0.1:6379> bf.exists bookname java
(integer) 1
127.0.0.1:6379> bf.exists bookname python
(integer) 1
127.0.0.1:6379> bf.exists bookname c#
(integer) 1
127.0.0.1:6379> bf.madd bookname js css html
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.mexists bookname js css html spring
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 0

Redis 提供了可以自定义参数的布隆过滤器,需要在 add 之前通过bf.reserve 指令显式创建。
bf.reserve 有三个参数,分别是 key、error_rate (错误率) 和 initial_size:

  • error_rate 越低,需要的空间越大,对于不需要过于精确的场合,设置稍大一些也没有关系,比如上面说的推送系统,只会让一小部分的内容被过滤掉,整体的观看体验还是不会受到很大影响的;
  • initial_size 表示预计放入的元素数量,当实际数量超过这个值时,误判率就会提升,所以需要提前设置一个较大的数值避免超出导致误判率升高

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);
// 判断指定元素是否存在,如果mightContain 方法返回false,一定不存在,如果返回true,99% 确定存在。
System.out.println(filter.mightContain(a));
System.out.println(filter.mightContain(b));
// 将元素添加进布隆过滤器
filter.put(1);
filter.put(2);
System.out.println(filter.mightContain(a));
System.out.println(filter.mightContain(b));

限制

  • 受容量限制。
  • Guava 中的布隆过滤器只能单机使用,分布式场景下只能使用 Redis 中的布隆过滤器了。
  • 多个应用存在多个布隆过滤器,构建同步复杂。 容器一和容器二中的布隆过滤器无法同步。
    【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器_第4张图片

Redis 分布式布隆过滤器实现

【Redis】15. 布隆过滤器(Bloom Filter) — 亿级别的过滤器_第5张图片
代码稍后补上!

你可能感兴趣的:(Redis)