《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【11】篇,最新系列文章请前往公众号“zxiaofan”查看,或百度搜索“玩转Redis zxiaofan”即可。
往期精选:《玩转Redis-HyperLogLog原理探索》
本文关键字:玩转Redis、Bloom filter、布隆过滤器、无偏hash函数;
大纲
- 布隆过滤器介绍
- 什么是布隆过滤器
- 布隆过滤器有什么特性
- Redis布隆过滤器实战
- rebloom的安装
- 布隆过滤器的命令详解及示例
- 布隆过滤器的底层原理
- 布隆过滤器的底层结构
- 最佳hash函数数量与错误率的关系
- 所需存储空间与错误率及容量关系
- 布隆过滤器如何扩容
- 布隆过滤器有哪些应用场景
- 布隆过滤器的优缺点
- 延伸拓展
1、布隆过滤器介绍
先前我们学习了HyperLogLog(传送门《玩转Redis-HyperLogLog原理探索》《玩转Redis-HyperLogLog统计微博日活月活》),非常适合大数据下的基数计算场景,但其有个缺陷,无法判断某个值是否已存在。
Hash、Set、String的BitMap等可以实现判断元素是否存在的功能,但这些实现方式要么随着元素增多会占用大量内存(Hash、Set),要么无法动态伸缩和保持误判率不变(BitMap)。因此,我们非常需要一种可以高效判断大量数据是否存在且允许一定误判率的数据结构。
1.1、什么是布隆过滤器(Bloom Filter)
布隆过滤器由Burton Howard Bloom于1970年提出,用于判断一个元素是否在集合中。
布隆过滤器(Bloom filter)是一种非常节省空间的概率数据结构(space-efficient probabilistic data structure),运行速度快(时间效率),占用内存小(空间效率),但是有一定的误判率且无法删除元素。本质上由一个很长的二进制向量和一系列随机映射函数组成。
1.2 布隆过滤器有什么特性
- 检查一个元素是否在集成中;
- 检查结果分为2种:一定不在集合中、可能在集合中;
- 布隆过滤器支持添加元素、检查元素,但是不支持删除元素;
- 检查结果的“可能在集合中”说明存在一定误判率;
- 已经添加进入布隆过滤器的元素是不会被误判的,仅未添加过的元素才可能被误判;
- 相比set、Bitmaps非常节省空间:因为只存储了指纹信息,没有存储元素本身;
- 添加的元素超过预设容量越多,误报的可能性越大。
2、Redis布隆过滤器实战
2.1、rebloom的安装
还没有安装Redis的同学,可以参考我先前的文章安装,传送门《玩转Redis-Redis安装、后台启动、卸载》。Redis 4.0开始以插件形式提供布隆过滤器。
# docker方式安装
> docker pull redislabs/rebloom # 拉取镜像
> docker run -p6379:6379 redislabs/rebloom # 运行容器
> redis-cli # 连接容器中的 redis 服务
# linux服务器直接安装
>git clone git://github.com/RedisLabsModules/rebloom
>cd rebloom
>make
# 当前路径会生成一个rebloom.so文件
# 在redis的配置中(通常在/etc/redis/redis.conf)增加一行配置 loadmodule /"rebloom.so的绝对路径"/rebloom.so
# 重启Redis即可
上述的安装提到需要重启Redis,但是生产环境的Redis可不是你想重启就重启的。有什么方式可以不重启Redis就加载rebloom插件吗,MODULE LOAD命令就派上用场了。
# 不重启Redis加载rebloom插件
1、查看redis当前已加载的插件
> MODULE LOAD /"rebloom.so的绝对路径"/redisbloom.so
> module list
1) 1) "name"
2) "bf"
3) "ver"
4) (integer) 999999
# 看到以上数据则说明redisbloom加载成功了,模块名name为"bf",模块版本号ver为999999。
# 动态执行模块卸载
# MODULE UNLOAD 模块名
# 当然,为了防止Redis重启导致动态加载的模块丢失,我们还是应该在redis.conf 中加上相关配置。
2.2、布隆过滤器的命令详解及示例
完整指令说明可前往官网查看:https://oss.redislabs.com/redisbloom/Bloom_Commands/。
2.2.1、Bloom命令简述
【核心命令】添加元素:BF.ADD(添加单个)、BF.MADD(添加多个)、BF.INSERT(添加多个);
【核心命令】检查元素是否存在:BF.EXISTS(查询单个元素)、BF.MEXISTS(查询多个元素)
命令 | 功能 | 参数 |
---|---|---|
BF.RESERVE | 创建一个大小为capacity,错误率为error_rate的空的Bloom | BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion] [NONSCALING] |
BF.ADD | 向key指定的Bloom中添加一个元素item | BF.ADD {key} {item} |
BF.MADD | 向key指定的Bloom中添加多个元素 | BF.MADD {key} {item} [item...] |
BF.INSERT | 向key指定的Bloom中添加多个元素,添加时可以指定大小和错误率,且可以控制在Bloom不存在的时候是否自动创建 | BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION expansion] [NOCREATE] [NONSCALING] ITEMS {item...} |
BF.EXISTS | 检查一个元素是否可能存在于key指定的Bloom中 | BF.EXISTS {key} {item} |
BF.MEXISTS | 同时检查多个元素是否可能存在于key指定的Bloom中 | BF.MEXISTS {key} {item} [item...] |
BF.SCANDUMP | 对Bloom进行增量持久化操作 | BF.SCANDUMP {key} {iter} |
BF.LOADCHUNK | 加载SCANDUMP持久化的Bloom数据 | BF.LOADCHUNK {key} {iter} {data} |
BF.INFO | 查询key指定的Bloom的信息 | BF.INFO {key} |
BF.DEBUG | 查看BloomFilter的内部详细信息(如每层的元素个数、错误率等) | BF.DEBUG {key} |
2.2.2、BF.RESERVE
- 参数
- BF.RESERVE {key} {error_rate} {capacity}
- 功能
- 创建一个大小为capacity,错误率为error_rate的空的BloomFilter
- 时间复杂度
- O(1)
- 参数说明
- key:布隆过滤器的key;
- error_rate:期望的错误率(False Positive Rate),该值必须介于0和1之间。该值越小,BloomFilter的内存占用量越大,CPU使用率越高。
- capacity:布隆过滤器的初始容量,即期望添加到布隆过滤器中的元素的个数。当实际添加的元素个数超过该值时,布隆过滤器将进行自动的扩容,该过程会导致性能有所下降,下降的程度是随着元素个数的指数级增长而线性下降。
- 可选参数
- expansion:当添加到布隆过滤器中的数据达到初始容量后,布隆过滤器会自动创建一个子过滤器,子过滤器的大小是上一个过滤器大小乘以expansion。expansion的默认值是2,也就是说布隆过滤器扩容默认是2倍扩容。
- NONSCALING:设置此项后,当添加到布隆过滤器中的数据达到初始容量后,不会扩容过滤器,并且会抛出异常((error) ERR non scaling filter is full)。
- 返回值
- 成功:OK;
- 其它情况返回相应的异常信息。
- 备注
- BloomFilter的扩容是通过增加BloomFilter的层数来完成的。每增加一层,在查询的时候就可能会遍历多层BloomFilter来完成,每一层的容量都是上一层的两倍(默认)。
# 公众号@zxiaofan
# 创建一个容量为5且不允许扩容的过滤器;
127.0.0.1:6379> bf.reserve bf2 0.1 5 NONSCALING
OK
127.0.0.1:6379> bf.madd bf2 1 2 3 4 5
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1
# 添加第6个元素时即提示BloomFilter已满;
127.0.0.1:6379> bf.madd bf2 6
1) (error) ERR non scaling filter is full
127.0.0.1:6379> bf.info bf2
1) Capacity
2) (integer) 5
3) Size
4) (integer) 155
5) Number of filters
6) (integer) 1
7) Number of items inserted
8) (integer) 5
9) Expansion rate
10) (integer) 2
2.2.3、BF.ADD
- 参数
- BF.ADD {key} {item}
- 功能
- 向key指定的Bloom中添加一个元素item。
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- item:待插入过滤器的元素;
- 返回值
- 元素不存在插入成功:返回1;
- 元素可能已经存在:返回0;
- 其它情况返回相应的异常信息。
2.2.3、BF.MADD
- 参数
- BF.MADD {key} {item} [item...]
- 功能
- 向key指定的Bloom中添加多个元素item。
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- item:待插入过滤器的元素,可插入多个;
- 返回值
- 成功:返回一个数组,数组的每一个元素可能为1或0,当item一定不存在时数组元素值为1,当item可能已经存在时数组元素值为0。
- 其它情况返回相应的异常信息。
2.2.5、BF.EXISTS
- 参数
- BF.EXISTS {key} {item}
- 功能
- 检查一个元素是否可能存在于key指定的Bloom中
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- item:待检查的元素;
- 返回值
- 元素一定不存在:0;
- 元素可能存在:1;
- 其它情况返回相应的异常信息。
2.2.6、BF.MEXISTS
- 参数
- BF.MEXISTS <key> <item> [item...]
- 功能
- 检查多个元素是否可能存在于key指定的Bloom中
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- item:待检查的元素,可设置多个;
- 返回值
- 成功:返回一个数组,数组的每一个元素可能为1或0,当item一定不存在时数组元素值为0,当item可能已经存在时数组元素值为1。
- 其它情况返回相应的异常信息。
# 公众号@zxiaofan
# 向BloomFilter添加单个元素
127.0.0.1:6379> bf.add bf1 itemadd1
(integer) 1
# 向BloomFilter批量添加多个元素
127.0.0.1:6379> bf.madd bf1 itemmadd1 itemmadd2
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.exists itemmadd1
(error) ERR wrong number of arguments for 'bf.exists' command
127.0.0.1:6379> bf.exists bf1 itemmadd1
(integer) 1
# 批量检查多个元素是否存在于BloomFilter
127.0.0.1:6379> bf.mexists bf1 itemadd1 itemmadd1 itemmadd2
1) (integer) 1
2) (integer) 1
3) (integer) 1
```c
### 2.2.7、BF.INSERT
- 参数
- BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION expansion] [NOCREATE] [NONSCALING] ITEMS {item...}
- 功能
- 向key指定的Bloom中添加多个元素,添加时可以指定大小和错误率,且可以控制在Bloom不存在的时候是否自动创建
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- CAPACITY:[如果过滤器已创建,则此参数将被忽略]。更多的信息参考<bf.reserve>;
- ERROR:[如果过滤器已创建,则此参数将被忽略]。更多的信息参考<bf.reserve>;
- expansion:布隆过滤器会自动创建一个子过滤器,子过滤器的大小是上一个过滤器大小乘以expansion。expansion的默认值是2,也就是说布隆过滤器扩容默认是2倍扩容。
- NOCREATE:如果设置了该参数,当布隆过滤器不存在时则不会被创建。用于严格区分过滤器的创建和元素插入场景。该参数不能与CAPACITY和ERROR同时设置。
- NONSCALING:设置此项后,当添加到布隆过滤器中的数据达到初始容量后,不会扩容过滤器,并且会抛出异常((error) ERR non scaling filter is full)。
- ITEMS:待插入过滤器的元素列表,该参数必传。
- 返回值
- 成功:返回一个数组,数组的每一个元素可能为1或0,当item一定不存在时数组元素值为1,当item可能已经存在时数组元素值为0。
- 其它情况返回相应的异常信息。
```c
127.0.0.1:6379> del bfinsert
(integer) 1
127.0.0.1:6379> bf.insert bfinsert CAPACITY 5 ERROR 0.1 EXPANSION 2 NONSCALING ITEMS item1 item2
1