redis bloom filter-功能介绍和原理

[toc]

快速安装体验

build

git clone https://github.com/RedisBloom/RedisBloom.git
cd redisbloom
make
-----
以上命令会生成redisbloom.so文件

动态load redisbloom

# MODULE LOAD /redisbloom.so (编译出的so路径)
查看已加载的插件module list
1) 1) "name"  插件名字
   2) "bf"    模块名
   3) "ver"   模块版本号
   4) (integer) 999999 
# 动态执行模块卸载
# MODULE UNLOAD 模块名

启动加载

# Assuming you have a redis build from the unstable branch:
./redis-server --loadmodule ./redisbloom.so (编译出的so路径)

redis-server --loadmodule /path/to/redisbloom.so INITIAL_SIZE 400 ERROR_RATE 0.004
The default error rate is 0.01 and the default initial capacity is 100 .

RedisBloom-func

参数设置

BF.RESERVE

Format:BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]
eg:bf.reserve key3 0.1 5 NONSCALING
OK
127.0.0.1:6379> bf.add key3 0
(integer) 1
127.0.0.1:6379> bf.add key3 1
(integer) 1
127.0.0.1:6379> bf.add key3 2
(integer) 1
127.0.0.1:6379> bf.add key3 3
(integer) 1
127.0.0.1:6379> bf.add key3 4
(integer) 1
127.0.0.1:6379> bf.add key3 5
(error) ERR non scaling filter is full
容量设置为5,且配置为不可以扩容,添加第6个元素时即提示BloomFilter is full。

Parameters:

  • key:filter 名字
  • error_rate:期望错误率,期望错误率越低,需要的空间就越大。
  • capacity:初始容量,当实际元素的数量超过这个初始化容量时,误判率上升。
    可选参数
  • EXPANSION:当添加到布隆过滤器中的数据达到初始容量后,布隆过滤器会自动创建一个子过滤器,子过滤器的大小是上一个过滤器大小乘以expansion;expansion的默认值是2,也就是说布隆过滤器扩容默认是2倍扩容
  • NONSCALING:设置此项后,当添加到布隆过滤器中的数据达到初始容量后,不会扩容过滤器,并且会抛出异常((error) ERR non scaling filter is full)
    说明:BloomFilter的扩容是通过增加BloomFilter的层数来完成的。每增加一层,在查询的时候就可能会遍历多层BloomFilter来完成,每一层的容量都是上一层的两倍(默认)。默认的error_rate是 0.01,capacity是 100

添加item操作

BF.ADD

BF.ADD {key} {item}
eg:BF.ADD key0 v0
(integer) 1

功能:向key指定的Bloom中添加一个元素

  • key:filter 名字
  • item:单个元素
  • 返回值:1:新添加, 0:已经被添加过,如果设置了capacity且配置为不可以扩容,会返回(error) ERR non scaling filter is full

BF.MADD

BF.MADD {key} {item ...}
eg:BF.ADD key0 v1 v2
1) (integer) 1
2) (integer) 1

功能:向key指定的Bloom中添加多个元素

  • key:filter 名字
  • item:单个或者多个元素
  • 返回值(数组):1:新添加, 0:已经被添加过,如果设置了capacity且配置为不可以扩容,会返回(error) ERR non scaling filter is full

BF.INSERT

BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION {expansion}] [NOCREATE] [NONSCALING] ITEMS {item ...}
eg: bf.insert bfinKey0 CAPACITY 5 ERROR 0.1 EXPANSION 2  NONSCALING ITEMS item1 item2
1) (integer) 1
2) (integer) 1

功能:向key指定的Bloom中添加多个元素,添加时可以指定大小和错误率,且可以控制在Bloom不存在的时候是否自动创建
参数说明

  • key:filter 名字
  • CAPACITY:[如果过滤器已创建,则此参数将被忽略]。
  • ERROR:[如果过滤器已创建,则此参数将被忽略]。
  • expansion:布隆过滤器会自动创建一个子过滤器,子过滤器的大小是上一个过滤器大小乘以expansion。expansion的默认值是2,也就是说布隆过滤器扩容默认是2倍扩容。
  • NOCREATE:如果设置了该参数,当布隆过滤器不存在时则不会被创建。用于严格区分过滤器的创建和元素插入场景。该参数不能与CAPACITY和ERROR同时设置。
  • NONSCALING:设置此项后,当添加到布隆过滤器中的数据达到初始容量后,不会扩容过滤器,并且会抛出异常((error) ERR non scaling filter is full)。
  • ITEMS:待插入过滤器的元素列表,该参数必传。

检测item

BF.EXISTS

BF.EXISTS {key} {item}
eg:BF.EXISTS key0 v1
(integer) 1

功能:检查一个元素是否存在于BloomFilter

  • key:filter 名字
  • item:一个值
  • 返回值:1:存在, 0:不存在

BF.MEXISTS

BF.MEXISTS {key} {item}
eg:BF.MEXISTS key0 v1 v2
1) (integer) 1
2) (integer) 1

功能:批量检查多个元素是否存在于BloomFilter

  • key:filter 名字
  • item:一个或者多个值
  • 返回值(数组):1:存在, 0:不存在

其他

BF.SCANDUMP

BF.SCANDUMP {key} {iter}
eg:BF.SCANDUMP key0 0
1) (integer) 1
2) "\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x02\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\x80\x04\x00\x00\x00\x00\x00\x00\x04\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"

功能:对Bloom进行增量持久化操作(增量保存)

  • key:filter 名字
  • iter:首次调用传值0,或者上次调用此命令返回的结果值;
  • 返回值:返回连续的(iter, data)对,直到(0,NULL),表示DUMP完成

BF.LOADCHUNK

BF.LOADCHUNK {key} {iter} {data}

功能:加载SCANDUMP持久化的Bloom数据

  • key:目标布隆过滤器的名字;
  • iter:SCANDUMP返回的迭代器的值,和data一一对应;
  • data:SCANDUMP返回的数据块(data chunk);

BF.INFO

BF.INFO {key}   
eg:bf.info key1
 1) Capacity 
 2) (integer) 7
 3) Size
 4) (integer) 416
 5) Number of filters
 6) (integer) 3
 7) Number of items inserted
 8) (integer) 5
 9) Expansion rate
10) (integer) 2

功能:查询key指定的Bloom的信息
返回值:

  • Capacity:预设容量;
  • Size:实际占用情况,但如何计算待进一步确认;
  • Number of filters:过滤器层数;
  • Number of items inserted:已经实际插入的元素数量;
  • Expansion rate:子过滤器扩容系数(默认2);

BF.DEBUG

BF.DEBUG {key}
eg:bf.debug key1
1) "size:5"
2) "bytes:8 bits:64 hashes:5 hashwidth:64 capacity:1 size:1 ratio:0.05"
3) "bytes:8 bits:64 hashes:6 hashwidth:64 capacity:2 size:2 ratio:0.025"
4) "bytes:8 bits:64 hashes:7 hashwidth:64 capacity:4 size:2 ratio:0.0125"

功能:查看BloomFilter的内部详细信息(如每层的元素个数、错误率等)
返回值:

  • 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无关);

扩展

RedisBloom工作原理简述

  • hash
image.png

A Bloom filter is an array of many bits. When an element is ‘added’ to a bloom filter, the element is hashed. Then bit[hashval % nbits] is set to 1

  • 减少hash冲突
image.png

In order to reduce the risk of collisions, an entry may use more than one bit

  • 举例


    image.png

RedisBloom hash函数数量与错误率的关系

源码hash函数数量计算公式

int bloom_init(struct bloom *bloom, uint64_t entries, double error, unsigned options) {
    // ...
    bloom->bpe = calc_bpe(error);
    bloom->hashes = (int)ceil(0.693147180559945 * bloom->bpe); // ln(2) 
    // ...
}
static double calc_bpe(double error) {
    static const double denom = 0.480453013918201; // ln(2)^2
    double num = log(error);

    double bpe = -(num / denom);
    if (bpe < 0) {
        bpe = -bpe;
    }
    return bpe;
}

// Math.ceil() 函数返回大于或等于一个给定数字的最小整数
// ln(2) ≈ 0.693147180559945
// ln(2)^2 ≈ 0.480453013918201
// log(error):以10为底的对数函数

即RedisBloom计算hash函数的个数k =  - log(error) / ( (ln2) ^2) * ln(2) )
符合bloomfilter的推倒公式:[布隆过滤器 (Bloom Filter) 详解](https://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html)

结论

  • 错误率越低,需要的hash函数越多

可以通过命令bf.reserve和bf.debug创建和查看redis bloom中最佳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
eg:
bf.reserve bf0.1-2 0.1 100
bf.debug bf0.1-2
1) "size:0"
2) "bytes:80 bits:640 hashes:5 hashwidth:64 capacity:100 size:0 ratio:0.05"

RedisBloom存储空间与错误率及容量关系

源码计算公式

int bloom_init(struct bloom *bloom, uint64_t entries, double error, unsigned options) {
    // ...
  bloom->bpe = calc_bpe(error);
  bits = bloom->bits = (uint64_t)(entries * bloom->bpe);
  // ...
}
即:bits = (entries * ln(error)) / ln(2)^2

结论

  • 错误率{error_rate}越小,所需的存储空间越大; 初始化设置的元素数量{capacity}越大,所需的存储空间越大,当然如果实际远多于预设时,准确率就会降低。
错误率{error_rate} 元素数量{capacity} 占用内存(单位M)
0.01 10万 0.13146M (bytes:137848)
0.01 1百万 1.3146M (bytes:137847)
0.01 1千万 13.146M (bytes:13784696)
0.001 10万 0.18859M (bytes:197760)
0.001 1百万 1.8859M(bytes:1977536)
0.001 1千万 18.859M(bytes:19775360)
0.0001 10万 2.4572M (bytes:2576608)
0.0001 1百万 24.572M (bytes:25766016)
0.0001 1千万 245.72M (bytes:257660152)

RedisBloom官方默认的error_rate是 0.01,默认的capacity是 100

RedisBloom扩容机制

实验

1、创建一个容量为5的RedisBloom bf.reserve keyExp 0.1 5
   
2、添加5个bf.madd keyExp 1 2 3 4 5   
   bf.debug keyExp
   1) "size:5"
   2) "bytes:8 bits:64 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
3、重复添加“1” bf.madd keyExp 1 
   查看RedisBloom状态,未发生扩容
   bf.debug keyExp
   1) "size:5"
   2) "bytes:8 bits:64 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
   
4、添加第六6key bf.madd keyExp 6 
   查看RedisBloom状态,发现发生扩容了
   bf.debug keyExp
   1) "size:6"
   2) "bytes:8 bits:64 hashes:5 hashwidth:64 capacity:5 size:5 ratio:0.05"
   3) "bytes:16 bits:128 hashes:6 hashwidth:64 capacity:10 size:1 ratio:0.025"

结论

1.插入m个元素,计算实际插入BloomFilter的元素数量;
2.如果实际插入元素数量 > BloomFilter的容量,则触发扩容;
3.扩容的倍数为BloomFilter初始化时设置的expansion(默认2);

备注:

  • 扩容触发的条件是实际插入 > 容量,实际插入数量 = 容量时,是不会触发扩容
  • 实际插入指的是插入成功,即使计划插入的数据过滤器中没有,但由于hash冲突导入插入失败,这种也不算实际插入成功。

RedisBloom压测

Redis-benchmark是Redis官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能,Redis-benchmark参数的使用说明如下所示。

Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

 -h <hostname>      Server hostname (default 127.0.0.1)
 -p <port>          Server port (default 6379)
 -s <socket>        Server socket (overrides host and port)
 -a <password>      Password for Redis Auth
 -c <clients>       Number of parallel connections (default 50)
 -n <requests>      Total number of requests (default 100000)
 -d <size>          Data size of SET/GET value in bytes (default 2)
 --dbnum <db>        SELECT the specified db number (default 0)
 -k <boolean>       1=keep alive 0=reconnect (default 1)
 -r <keyspacelen>   Use random keys for SET/GET/INCR, random values for SADD
  Using this option the benchmark will expand the string __rand_int__
  inside an argument with a 12 digits number in the specified range
  from 0 to keyspacelen-1. The substitution changes every time a command
  is executed. Default tests use this to hit random keys in the
  specified range.
 -P <numreq>        Pipeline <numreq> requests. Default 1 (no pipeline).
 -e                 If server replies with errors, show them on stdout.
                    (no more than 1 error per second is displayed)
 -q                 Quiet. Just show query/sec values
 --csv              Output in CSV format
 -l                 Loop. Run the tests forever
 -t <tests>         Only run the comma separated list of tests. The test
                    names are the same as the ones produced as output.
 -I                 Idle mode. Just open N idle connections and wait.

参考文档

RedisBloom

Bloom Filter Datatype for Redis

Redis 6.0与老版性能对比评测

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容