Redis中布隆过滤器的使用及原理
《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【11】篇,最新系列文章请前往公众号“zxiaofan”查看,或百度搜索“玩转Redis zxiaofan”即可。
往期精选:《玩转Redis-HyperLogLog原理探索》
本文关键字:玩转Redis、Bloom filter、布隆过滤器、无偏hash函数;
大纲
- 布隆过滤器介绍
- 什么是布隆过滤器
- 布隆过滤器有什么特性
- Redis布隆过滤器实战
- rebloom的安装
- 布隆过滤器的命令详解及示例
- 布隆过滤器的底层原理
- 布隆过滤器的底层结构
- 最佳hash函数数量与错误率的关系
- 所需存储空间与错误率及容量关系
- 布隆过滤器如何扩容
- 布隆过滤器有哪些应用场景
- 布隆过滤器的优缺点
- 延伸拓展
1、布隆过滤器介绍
1.1、什么是布隆过滤器(Bloom Filter)
布隆过滤器由Burton Howard Bloom于1970年提出,用于判断一个元素是否在集合中。
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 bf21) Capacity2) (integer) 53) Size4) (integer) 1555) Number of filters6) (integer) 17) Number of items inserted8) (integer) 59) 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 [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
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。
- 其它情况返回相应的异常信息。
127.0.0.1:6379> del bfinsert
(integer) 1127.0.0.1:6379> bf.insert bfinsert CAPACITY 5 ERROR 0.1 EXPANSION 2 NONSCALING ITEMS item1 item2
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.exists bfinsert item5
(integer) 0
127.0.0.1:6379> bf.insert bfinsert CAPACITY 5 ERROR 0.1 EXPANSION 2 NONSCALING ITEMS item1 item2 item3 item4 item5
1) (integer) 0
2) (integer) 0
3) (integer) 1
4) (integer) 1
5) (integer) 0
127.0.0.1:6379> bf.add bfinsert item5
(integer) 0
127.0.0.1:6379> bf.info bfinsert1) Capacity2) (integer) 53) Size4) (integer) 1555) Number of filters6) (integer) 17) Number of items inserted8) (integer) 49) Expansion rate
10) (integer) 2
127.0.0.1:6379> bf.add bfinsert item6
(integer) 1
127.0.0.1:6379> bf.add bfinsert item5
(integer) 0
127.0.0.1:6379> bf.exists bfinsert item5
(integer) 1# 这里有个比较有意思的现象,item5未显示添加成功,但是后续却显示exists
# 这说明发生了hash冲突,误判就是这样产生的。
2.2.8、BF.SCANDUMP
- 参数
- BF.SCANDUMP {key} {iter}
- 功能
- 对Bloom进行增量持久化操作(增量保存);
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:布隆过滤器的名字;
- iter:首次调用传值0,或者上次调用此命令返回的结果值;
- 返回值
- 返回连续的(iter, data)对,直到(0,NULL),表示DUMP完成。
- 备注
127.0.0.1:6378> bf.madd bfdump d1 d2 d3 d4 d5 d6 d7
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1
6) (integer) 1
7) (integer) 1
127.0.0.1:6378> bf.scandump bfdump 0
1) (integer) 1
2) "\a\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x02\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x00\x00P\x04\x00\x00\x00\x00\x00\x00\a\x00\x00\x00\x00\x00\x00\x00{\x14\xaeG\xe1zt?\xe9\x86/\xb25\x0e&@\b\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x00"
127.0.0.1:6378> bf.scandump bfdump 1
1) (integer) 139
2) "\x80\x00\b\n\x00$\x00 \b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\x82$\x04\x00@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x04\x00\x00\x00\x00\x00\x00\x04\x01@\xa0\x00@\x00\x00\x00\x00\x00\x10@\x00\x02\"\x00 \x00\x00\x04\x00\x00\x00\x00\x00 \x00\x80\x00\x00\"\x04\x04\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00 \x80$\x00 \x00\x00 \x0c$\x00\x00\x00\b`\x00\x00\x00\x00\x00\x00\x00\x00\b\x80\x02 \x04\x00\x00\x00\x00\x00"
127.0.0.1:6378> bf.scandump bfdump 139
1) (integer) 0
2) ""
2.2.9、BF.LOADCHUNK
- 参数
- BF.LOADCHUNK {key} {iter} {data}
- 功能
- 加载SCANDUMP持久化的Bloom数据;
- 时间复杂度
- O(log N),N是过滤器的层数。
- 参数说明
- key:目标布隆过滤器的名字;
- iter:SCANDUMP返回的迭代器的值,和data一一对应;
- data:SCANDUMP返回的数据块(data chunk);
- 返回值
- 成功则返回OK。
# Python 伪代码
# 来源于:https://oss.redislabs.com/redisbloom/Bloom_Commands/chunks = []
iter = 0
# SCANDUMP
while True:iter, data = BF.SCANDUMP(key, iter)if iter == 0:breakelse:chunks.append([iter, data])# LOADCHUNK
for chunk in chunks:iter, data = chunkBF.LOADCHUNK(key, iter, data)
2.2.10、BF.INFO
- 参数
- BF.INFO {key}
- 功能
- 返回BloomFilter的相关信息;
- 时间复杂度
- O(1);
- 参数说明
- key:目标布隆过滤器的名字;
- 返回值
- Capacity:预设容量;
- Size:实际占用情况,但如何计算待进一步确认;
- Number of filters:过滤器层数;
- Number of items inserted:已经实际插入的元素数量;
- Expansion rate:子过滤器扩容系数(默认2);
127.0.0.1:6379> bf.info bf21) Capacity2) (integer) 53) Size4) (integer) 1555) Number of filters6) (integer) 17) Number of items inserted8) (integer) 59) Expansion rate
10) (integer) 2
2.2.11、BF.DEBUG
- 参数
- BF.DEBUG {key}
- 功能
- 查看BloomFilter的内部详细信息(如每层的元素个数、错误率等);
- 时间复杂度
- O(log N),N是过滤器的层数;
- 参数说明
- key:目标布隆过滤器的名字;
- 返回值
- size:BloomFilter中已插入的元素数量;
- 每层BloomFilter的详细信息
- bytes:占用字节数量;
- bits:占用bit位数量,bits = bytes * 8;
- hashes:该层hash函数数量;
- hashwidth:hash函数宽度;
- capacity:该层容量(第一层为BloomFilter初始化时设置的容量,第2层容量 = 第一层容量 * expansion,以此类推);
- size:该层中已插入的元素数量(各层size之和等于BloomFilter中已插入的元素数量size);
- ratio:该层错误率(第一层的错误率 = BloomFilter初始化时设置的错误率 * 0.5,第二层为第一层的0.5倍,以此类推,ratio与expansion无关);
# 公众号 @zxiaofan
# 创建一个容量为5的BloomFilter,其key为“bfexp”;
127.0.0.1:6379> bf.reserve bfexp 0.1 5
OK# 查看BloomFilter的内部信息,此时BloomFilter的层数为1
127.0.0.1:6379> bf.debug bfexp
1) "size:0"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:0 ratio:0.05"127.0.0.1:6379> bf.madd bfexp 1 2 3 4 5
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1
127.0.0.1:6379> bf.debug bfexp
1) "size:5"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"127.0.0.1:6379> bf.madd bfexp 11 12 13 14 15
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 0
5) (integer) 1# 添加10个元素后,此时BloomFilter的层数变为2;
# BloomFilter的元素数量为2层过滤器之和(5+4=9),添加“14”时实际因为hash冲突没添加成功;
127.0.0.1:6379> bf.debug bfexp
1) "size:9"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:4 ratio:0.025"127.0.0.1:6379> bf.madd bfexp 21 22 23
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.debug bfexp
1) "size:12"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:7 ratio:0.025"
127.0.0.1:6379> bf.madd bfexp 24 25
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.debug bfexp
1) "size:14"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:9 ratio:0.025"
127.0.0.1:6379> bf.madd bfexp 31 32 33 34 35
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1# 添加20个元素后,此时BloomFilter的层数变为3;
127.0.0.1:6379> bf.debug bfexp
1) "size:19"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:10 ratio:0.025"
4) "bytes:23 bits:184 hashes:7 hashwidth:64 capacity:20 size:4 ratio:0.0125"
3、布隆过滤器的底层原理
3.1、布隆过滤器的底层结构
布隆过滤器本质是一个巨大的bit数组(bit array)+几个不同的无偏hash函数。
布隆过滤器添加一个item(“zxiaofan”),其操作步骤是:
- 使用多个无偏哈希函数对item进行hash运算,得到多个hash值hash(zxiaofan);
- 每个hash值对bit数组取模得到位数组中的位置index(zxiaofan);
- 判断所有index位是否都为1 ;
- 位都为1则说明该元素可能已经存在了;
- 任意一位不为1则说明一定不存在,此时会将不为1的位置为1;
需要注意的是,虽然使用了无偏hash函数,使得hash值尽可能均匀,但是不同的item计算出的hash值依旧可能重复,所以布隆过滤器返回元素存在,实际是有可能不存在的。
取模运算(“Modulus Operation”)和取余运算(“Remainder Operation ”)两个概念有重叠的部分但又不完全一致。主要的区别在于对负整数进行除法运算时操作不同。取模主要是用于计算机术语中。取余则更多是数学概念。a mod b = c,a、b符号一致时,取模、取余计算得出的C相同;a、b符号不一致时,取模计算的c其符号和b一致,取余计算的C其符号和a一致。
3.2、最佳hash函数数量与错误率的关系
# hash函数数量计算公式:# ceil(value):返回不小于value的最小整数;
# log(error):以10为底的对数函数;
# ln(x):以e为底的对数函数;
# ln(2) ≈ 0.693147180559945;
# ln(2)^2 ≈ 0.480453013918201;bloom->hashes = (int)ceil(0.693147180559945 * bloom->bpe);static double calc_bpe(double error) {static const double denom = 0.480453013918201; // ln(2)^2double num = log(error);double bpe = -(num / denom);if (bpe < 0) {bpe = -bpe;}return bpe;
}
我们通过创建不同错误率不同容量的布隆过滤器,整理hash函数数量与错误率的关系。
# 公众号@zxiaofan
# 创建一个key为“bf0.1-2”的布隆过滤器,其错误率为0.1,初始容量为100;
127.0.0.1:6379> bf.reserve bf0.1-2 0.1 100
OK
127.0.0.1:6379> bf.reserve bf0.1-3 0.1 1000
OK
127.0.0.1:6379> bf.reserve bf0.01-3 0.01 1000
OK
127.0.0.1:6379> bf.reserve bf0.01-4 0.01 10000
OK
127.0.0.1:6379> bf.reserve bf0.001-4 0.001 10000
OK
127.0.0.1:6379> bf.reserve bf0.001-5 0.001 100000
OK
127.0.0.1:6379> bf.reserve bf0.0001-5 0.0001 100000
OK
127.0.0.1:6379> bf.reserve bf0.00001-5 0.00001 100000
OK
127.0.0.1:6379> bf.reserve bf0.000001-5 0.000001 100000
OK
127.0.0.1:6379> bf.reserve bf0.000001-4 0.000001 10000
OK# 创建一个key为“bf0.0000001-4”的布隆过滤器,其错误率为0.0000001,初始容量为10000;
127.0.0.1:6379> bf.reserve bf0.0000001-4 0.0000001 10000
OK# 查看key为“bf0.1-2”的布隆过滤器信息,hashes表示内部使用的hash函数数量;
127.0.0.1:6379> bf.debug bf0.1-2
1) "size:0"
2) "bytes:78 bits:624 hashes:5 hashwidth:64 capacity:100 size:0 ratio:0.05"127.0.0.1:6379> bf.debug bf0.1-3
1) "size:0"
2) "bytes:780 bits:6240 hashes:5 hashwidth:64 capacity:1000 size:0 ratio:0.05"127.0.0.1:6379> bf.debug bf0.01-4
1) "size:0"
2) "bytes:13785 bits:110280 hashes:8 hashwidth:64 capacity:10000 size:0 ratio:0.005"127.0.0.1:6379> bf.debug bf0.001-5
1) "size:0"
2) "bytes:197754 bits:1582032 hashes:11 hashwidth:64 capacity:100000 size:0 ratio:0.0005"
# 197754 bytes = 197754/1024/1024 ≈ 0.19 M。 127.0.0.1:6379> bf.debug bf0.0001-5
1) "size:0"
2) "bytes:257661 bits:2061288 hashes:15 hashwidth:64 capacity:100000 size:0 ratio:5e-05"127.0.0.1:6379> bf.debug bf0.00001-5
1) "size:0"
2) "bytes:317567 bits:2540536 hashes:18 hashwidth:64 capacity:100000 size:0 ratio:5e-06"127.0.0.1:6379> bf.debug bf0.000001-5
1) "size:0"
2) "bytes:377474 bits:3019792 hashes:21 hashwidth:64 capacity:100000 size:0 ratio:5e-07"127.0.0.1:6379> bf.debug bf0.000001-4
1) "size:0"
2) "bytes:37748 bits:301984 hashes:21 hashwidth:64 capacity:10000 size:0 ratio:5e-07"127.0.0.1:6379> bf.debug bf0.0000001-4
1) "size:0"
2) "bytes:43738 bits:349904 hashes:25 hashwidth:64 capacity:10000 size:0 ratio:5e-08"
由上面的执行结果可以看出,Redis布隆过滤器中最佳hash函数数量与错误率的关系如下:
错误率{error_rate} | hash函数的最佳数量 |
---|---|
0.1 | 5 |
0.01 | 8 |
0.001 | 11 |
0.0001 | 15 |
0.00001 | 18 |
0.000001 | 21 |
0.0000001 | 25 |
3.3、所需存储空间与错误率及容量关系
通过创建不同错误率不同容量的布隆过滤器,整理存储空间与错误率及容量的关系。
127.0.0.1:6379> bf.reserve bf0.0001-6 0.0001 1000000
OK
127.0.0.1:6379> bf.reserve bf0.0001-7 0.0001 10000000
OK
127.0.0.1:6379> bf.reserve bf0.0001-8 0.0001 100000000
OK127.0.0.1:6379> bf.debug bf0.0001-6
1) "size:0"
2) "bytes:2576602 bits:20612816 hashes:15 hashwidth:64 capacity:1000000 size:0 ratio:5e-05"127.0.0.1:6379> bf.debug bf0.0001-7
1) "size:0"
2) "bytes:25766015 bits:206128120 hashes:15 hashwidth:64 capacity:10000000 size:0 ratio:5e-05"127.0.0.1:6379> bf.debug bf0.0001-8
1) "size:0"
2) "bytes:257660148 bits:2061281184 hashes:15 hashwidth:64 capacity:100000000 size:0 ratio:5e-05"
# 257660148 bytes = 257660148/1024/1024 ≈ 245.7 M。
错误率{error_rate} | 元素数量{capacity} | 占用内存(单位M) |
---|---|---|
0.001 | 10万 | 0.19 |
0.001 | 1百万 | 1.89 |
0.001 | 1千万 | 18.9 |
0.001 | 1亿 | 188.6 |
0.0001 | 10万 | 0.25 |
0.0001 | 1百万 | 2.5 |
0.0001 | 1千万 | 24.6 |
0.0001 | 1亿 | 245.7 |
0.00001 | 10万 | 0.3 |
0.00001 | 1百万 | 3.01 |
0.00001 | 1千万 | 30.1 |
0.00001 | 1亿 | 302.9 |
从上述对比分析可以看出,错误率{error_rate}越小,所需的存储空间越大; 初始化设置的元素数量{capacity}越大,所需的存储空间越大,当然如果实际远多于预设时,准确率就会降低。
在1千万数据场景下,error_rate为0.001、0.0001、0.00001实际占用内存都是30M以下,此时如果对准确性要求高,初始化时将错误率设置低一点是完全无伤大雅的。
RedisBloom官方默认的error_rate是 0.01,默认的capacity是 100,源码如下:
// RedisBloom/src/rebloom.cstatic double BFDefaultErrorRate = 0.01;
static size_t BFDefaultInitCapacity = 100;
3.4、布隆过滤器如何扩容
# 公众号 @zxiaofan
# 创建一个容量为5的BloomFilter,其key为“bfexp”;
127.0.0.1:6379> bf.reserve bfexp 0.1 5
OK# 查看BloomFilter的内部信息,此时BloomFilter的层数为1
127.0.0.1:6379> bf.debug bfexp
1) "size:0"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:0 ratio:0.05"127.0.0.1:6379> bf.madd bfexp 1 2 3 4 5
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1
127.0.0.1:6379> bf.debug bfexp
1) "size:5"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"127.0.0.1:6379> bf.madd bfexp 11 12 13 14 15
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 0
5) (integer) 1# 添加10个元素后,此时BloomFilter的层数变为2;
# BloomFilter的元素数量为2层过滤器之和(5+4=9),添加“14”时实际因为hash冲突没添加成功;
127.0.0.1:6379> bf.debug bfexp
1) "size:9"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:4 ratio:0.025"127.0.0.1:6379> bf.madd bfexp 21 22 23
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.debug bfexp
1) "size:12"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:7 ratio:0.025"
127.0.0.1:6379> bf.madd bfexp 24 25
1) (integer) 1
2) (integer) 1# 添加14个元素后,还未达到BloomFilter扩容阈值,层数依旧为2;
127.0.0.1:6379> bf.debug bfexp
1) "size:14"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:9 ratio:0.025"127.0.0.1:6379> bf.madd bfexp 31 32 33 34 35
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1# 添加20个元素后,此时BloomFilter的层数变为3;
127.0.0.1:6379> bf.debug bfexp
1) "size:19"
2) "bytes:4 bits:32 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3) "bytes:10 bits:80 hashes:6 hashwidth:64 capacity:10 size:10 ratio:0.025"
4) "bytes:23 bits:184 hashes:7 hashwidth:64 capacity:20 size:4 ratio:0.0125"
- 插入m个元素,计算实际插入BloomFilter的元素数量;
- 如果实际插入元素数量 > BloomFilter的容量,则触发扩容;
- 扩容的倍数为BloomFilter初始化时设置的expansion(默认2);
- 扩容触发的条件是 实际插入 > 容量,实际插入数量 = 容量时,是不会触发扩容的;
- 实际插入指的是插入成功,即使计划插入的数据过滤器中没有,但由于hash冲突导入插入失败,这种也不算实际插入成功。假设容量是20,如果插入21个元素,但由于重复甚至于hash冲突,导致实际插入的数量不足21个,此时也不会触发扩容;
4、布隆过滤器有哪些应用场景
4.1、邮件黑名单&网站黑名单
邮箱地址数十亿计且长度不固定,我们需要从海量的邮箱地址中识别出垃圾邮箱地址。当一个邮箱地址被判定为垃圾邮箱后,就将此地址添加进布隆过滤器中即可。
同理,万维网上的URL地址中包含了大量的非法或恶意URL,利用布隆过滤器也可以快速判断此类URL。当布隆过滤器返回结果为存在时,才对URL进行进一步判定处理。
4.2、新闻推荐去重
对于百度新闻、头条新闻等信息推荐平台,为了尽可能提升用户体验,应最大可能保证推荐给用户的新闻不重复,将已推荐给用户的文章ID存入布隆过滤器,再次推荐时先判断是否已推送即可。
4.3、缓存穿透&恶意攻击
缓存穿透:是指查询了缓存和数据库中都没有的数据。当此类查询请求量过大时(比如系统被恶意攻击),缓存系统或数据库的压力将增大,极容易宕机。
方式1:当查询DB中发现某数据不存在时,则将此数据ID存入布隆过滤器,每次查询时先判断是否存在于布隆过滤器,存在则说明数据库无此数据,无需继续查询了。当然此种方式仅能处理同一个ID重复访问的场景。
使用方式1需要防止指定ID最初不存在于DB中,遂将此ID存入“数据不存在的过滤器”中,但后续DB又新增了此ID,因为布隆过滤器不支持删除操作,一旦发生此类场景,就肯定会出现误判了。
使用方式2需要注意数据的增量,避免数据库中新增了数据而过滤器中还没有导致无法查询到数据。当然如果此时DB中删除了指定数据,布隆过滤器是无法随之删除指纹标记的。
了解了原理方能如臂使指。此外建议,生产数据的ID应定义生成规则及校验规则(比如身份证的最后一位就是校验位),这样每次查询先判断这个ID是否有效,有效才进行后续的步骤,这样可以充分过滤外部的恶意攻击。
4.4、网页爬虫URL去重
4.5、查询加速
4.6、防止重复请求
第一次请求,将请求参数放入布隆过滤器中,第二次请求时,先判断请求参数是否存在于BloomFilter中。
4.7、区块链应用
区块链中使用布隆过滤器来加快钱包同步;以太坊使用布隆过滤器用于快速查询以太坊区块链的日志。
以太坊记录交易日志也采用了布隆过滤器,以太坊的每个区块头包含当前区块中所有收据的日志的布隆过滤器logsBloom,便于高效查询日志数据。
5、布隆过滤器的优缺点
5.1、布隆过滤器的优势
5.2、布隆过滤器的缺点
- 【误判】:由于存在hash碰撞,匹配结果如果是“存在于过滤器中”,实际不一定存在;
- 【不可删除】:没有存储元素本身,所以只能添加但不可删除;
- 【空间利用率不高】:创建过滤器时需提前预估创建,当错误率越低时,为了尽可能避免hash碰撞,冗余的空间就越多;需要注意的是,空间利用率不高和节省空间并不冲突;
- 【容量满时误报率增加】当容量快满时,hash碰撞的概率变大,插入、查询的错误率也就随之增加了。
5.3、布隆过滤器其他问题
6、延伸拓展
6.1、超大规模布隆过滤器如何处理
【玩转Redis系列文章 近期精选 @zxiaofan】
《玩转Redis-HyperLogLog原理探索》
Redis中布隆过滤器的使用及原理相关推荐
- 玩转Redis-Redis中布隆过滤器的使用及原理
<玩转Redis>系列文章主要讲述Redis的基础及中高级应用.本文是<玩转Redis>系列第[11]篇,最新系列文章请前往公众号"zxiaofan"查 ...
- HBase学习笔记(三)——布隆过滤器(Bloom Filter)的原理
文章目录 布隆过滤器介绍 布隆过滤器原理 布隆过滤器的优缺点与用途 布隆过滤器使用场景 布隆过滤器介绍 布隆过滤器(Bloom Filter)由 Burton Howard Bloom 在 1970 ...
- js 数组 实现 完全树_Flink实例(六十八):布隆过滤器(Bloom Filter)的原理和实现 - 秋华...
什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...
- 布隆过滤器 布谷鸟过滤器 Redis 安装布隆过滤器
布隆过滤器 &布谷鸟过滤器 & Redis 安装布隆过滤器 1.布隆过滤器 1.1 简介 百度百科:布隆过滤器(Bloom Filter)是1970年由布隆提出的.它实际上是一个很长的 ...
- Redis进阶-布隆过滤器
文章目录 Pre 布隆能解决哪些问题? BloomFilter实现原理 构建布隆过滤器 构建布隆的误差率 实际误差率推算 布隆过滤器 (JVM级别) 布隆过滤器 (分布式) Bloom Filter的 ...
- Redis Guava 布隆过滤器实现和准确率测试
测试准确率 布隆过滤器的作用 判断这个数据是否存在于我们的集合中存在就是1 不存在就是0 它的底层实现就是一个二进制的数列 原理, 我们都知道hash算法, 如同给我们的数据加入了一个指纹, 但是ha ...
- python中布隆过滤器用法详解
1.布隆过滤器的介绍 布隆过滤器(Bloom Filter),是1970年,由一个叫布隆的小伙子提出的. 它实际上是一个很长的二进制向量和一系列随机映射函数,二进制大家应该都清楚,存储的数据不是0就是 ...
- mysql布隆过滤器源码_布隆过滤器(Bloom Filter)的原理和实现
什么情况下需要布隆过滤器? 先来看几个比较常见的例子 字处理软件中,需要检查一个英语单词是否拼写正确 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上 在网络爬虫里,一个网址是否被访问过 yahoo, ...
- 【redis】布隆过滤器详解
简介 本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),它实际上是一个很长的二进制向量和一系列随机映射函数. 布隆过滤器可以用于检索 ...
最新文章
- 联想g510拆键盘的简单方法_如何拆室内门锁?有方法太简单了
- JFreeChart的简单图表的制作------柱形图
- Linux IO模型漫谈(1)
- 还在为多集群管理烦恼吗?RedHat 和蚂蚁、阿里云给开源社区带来了OCM
- 51nod 1100:斜率最大
- 复杂指令集linux,精简指令集和复杂指令集区别
- 关于 matlab 的 s 函数的 DirFeedthrough
- Visual Studio 安装失败
- MongoDB-Getting Started with the C# Driver
- 【提问】iOS UIAtumator 是怎么判断元素isVisible的?
- 爬虫:如何爬取国家行政区划代码
- 嵌入式系统的性能评价
- Xen虚拟化环境安装和常用命令
- dp算法求解矩阵连乘的问题
- arcgis取消投影_【坐标系杂谈】投影后的数据如何去除投影?
- toft 测试用例rat_测试案例如何区分RAT,FAST,TOFT,FET | 学步园
- html meta标签的常用写法
- word2013图表题注:将图一-1改为图1-1
- python线程wait_Python线程指南
- Springboot—mysql+mybatis+generator插件
热门文章
- AOJ - 邻家割草(最小割)
- HDU - 4738 Caocao's Bridges(边双缩点)
- POJ - 1011 Sticks(dfs+剪枝)(好题!!)
- 剑指Offer-LeetCode刷题
- python同时输出多个值_python如何实现输出多个值?
- 天锋w2019_不知道为什么那么多人喜欢三星W2019,直到入手这款天锋W2019手机
- 秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock
- CImage类显示图片
- mediasoup-demo 运行实战
- C++ STL : 模拟实现STL中的容器适配器stack和queue