Redis中布隆过滤器的使用及原理
《玩轉(zhuǎn)Redis》系列文章主要講述Redis的基礎(chǔ)及中高級應用。本文是《玩轉(zhuǎn)Redis》系列第【11】篇,最新系列文章請前往公眾號“zxiaofan”查看,或百度搜索“玩轉(zhuǎn)Redis zxiaofan”即可。
往期精選:《玩轉(zhuǎn)Redis-HyperLogLog原理探索》
本文關(guān)鍵字:玩轉(zhuǎn)Redis、Bloom filter、布隆過濾器、無偏hash函數(shù);
大綱
- 布隆過濾器介紹
- 什么是布隆過濾器
- 布隆過濾器有什么特性
- Redis布隆過濾器實戰(zhàn)
- rebloom的安裝
- 布隆過濾器的命令詳解及示例
- 布隆過濾器的底層原理
- 布隆過濾器的底層結(jié)構(gòu)
- 最佳hash函數(shù)數(shù)量與錯誤率的關(guān)系
- 所需存儲空間與錯誤率及容量關(guān)系
- 布隆過濾器如何擴容
- 布隆過濾器有哪些應用場景
- 布隆過濾器的優(yōu)缺點
- 延伸拓展
1、布隆過濾器介紹
??先前我們學習了HyperLogLog(傳送門《玩轉(zhuǎn)Redis-HyperLogLog原理探索》《玩轉(zhuǎn)Redis-HyperLogLog統(tǒng)計微博日活月活》),非常適合大數(shù)據(jù)下的基數(shù)計算場景,但其有個缺陷,無法判斷某個值是否已存在。
??Hash、Set、String的BitMap等可以實現(xiàn)判斷元素是否存在的功能,但這些實現(xiàn)方式要么隨著元素增多會占用大量內(nèi)存(Hash、Set),要么無法動態(tài)伸縮和保持誤判率不變(BitMap)。因此,我們非常需要一種可以高效判斷大量數(shù)據(jù)是否存在且允許一定誤判率的數(shù)據(jù)結(jié)構(gòu)。
1.1、什么是布隆過濾器(Bloom Filter)
??布隆過濾器由Burton Howard Bloom于1970年提出,用于判斷一個元素是否在集合中。
??布隆過濾器(Bloom filter)是一種非常節(jié)省空間的概率數(shù)據(jù)結(jié)構(gòu)(space-efficient probabilistic data structure),運行速度快(時間效率),占用內(nèi)存小(空間效率),但是有一定的誤判率且無法刪除元素。本質(zhì)上由一個很長的二進制向量和一系列隨機映射函數(shù)組成。
1.2 布隆過濾器有什么特性
- 檢查一個元素是否在集成中;
- 檢查結(jié)果分為2種:一定不在集合中、可能在集合中;
- 布隆過濾器支持添加元素、檢查元素,但是不支持刪除元素;
- 檢查結(jié)果的“可能在集合中”說明存在一定誤判率;
- 已經(jīng)添加進入布隆過濾器的元素是不會被誤判的,僅未添加過的元素才可能被誤判;
- 相比set、Bitmaps非常節(jié)省空間:因為只存儲了指紋信息,沒有存儲元素本身;
- 添加的元素超過預設(shè)容量越多,誤報的可能性越大。
2、Redis布隆過濾器實戰(zhàn)
2.1、rebloom的安裝
?? 還沒有安裝Redis的同學,可以參考我先前的文章安裝,傳送門《玩轉(zhuǎn)Redis-Redis安裝、后臺啟動、卸載》。Redis 4.0開始以插件形式提供布隆過濾器。
# docker方式安裝> docker pull redislabs/rebloom # 拉取鏡像 > docker run -p6379:6379 redislabs/rebloom # 運行容器 > redis-cli # 連接容器中的 redis 服務(wù) # linux服務(wù)器直接安裝>git clone git://github.com/RedisLabsModules/rebloom >cd rebloom >make # 當前路徑會生成一個rebloom.so文件 # 在redis的配置中(通常在/etc/redis/redis.conf)增加一行配置 loadmodule /"rebloom.so的絕對路徑"/rebloom.so # 重啟Redis即可?? 上述的安裝提到需要重啟Redis,但是生產(chǎn)環(huán)境的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 # 看到以上數(shù)據(jù)則說明redisbloom加載成功了,模塊名name為"bf",模塊版本號ver為999999。# 動態(tài)執(zhí)行模塊卸載 # MODULE UNLOAD 模塊名# 當然,為了防止Redis重啟導致動態(tài)加載的模塊丟失,我們還是應該在redis.conf 中加上相關(guān)配置。2.2、布隆過濾器的命令詳解及示例
完整指令說明可前往官網(wǎng)查看:https://oss.redislabs.com/redisbloom/Bloom_Commands/。
2.2.1、Bloom命令簡述
【核心命令】添加元素:BF.ADD(添加單個)、BF.MADD(添加多個)、BF.INSERT(添加多個);
【核心命令】檢查元素是否存在:BF.EXISTS(查詢單個元素)、BF.MEXISTS(查詢多個元素)
| BF.RESERVE | 創(chuàng)建一個大小為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不存在的時候是否自動創(chuàng)建 | 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數(shù)據(jù) | BF.LOADCHUNK {key} {iter} {data} |
| BF.INFO | 查詢key指定的Bloom的信息 | BF.INFO {key} |
| BF.DEBUG | 查看BloomFilter的內(nèi)部詳細信息(如每層的元素個數(shù)、錯誤率等) | BF.DEBUG {key} |
2.2.2、BF.RESERVE
- 參數(shù)
- BF.RESERVE {key} {error_rate} {capacity}
- 功能
- 創(chuàng)建一個大小為capacity,錯誤率為error_rate的空的BloomFilter
- 時間復雜度
- O(1)
- 參數(shù)說明
- key:布隆過濾器的key;
- error_rate:期望的錯誤率(False Positive Rate),該值必須介于0和1之間。該值越小,BloomFilter的內(nèi)存占用量越大,CPU使用率越高。
- capacity:布隆過濾器的初始容量,即期望添加到布隆過濾器中的元素的個數(shù)。當實際添加的元素個數(shù)超過該值時,布隆過濾器將進行自動的擴容,該過程會導致性能有所下降,下降的程度是隨著元素個數(shù)的指數(shù)級增長而線性下降。
- 可選參數(shù)
- expansion:當添加到布隆過濾器中的數(shù)據(jù)達到初始容量后,布隆過濾器會自動創(chuàng)建一個子過濾器,子過濾器的大小是上一個過濾器大小乘以expansion。expansion的默認值是2,也就是說布隆過濾器擴容默認是2倍擴容。
- NONSCALING:設(shè)置此項后,當添加到布隆過濾器中的數(shù)據(jù)達到初始容量后,不會擴容過濾器,并且會拋出異常((error) ERR non scaling filter is full)。
- 返回值
- 成功:OK;
- 其它情況返回相應的異常信息。
- 備注
- BloomFilter的擴容是通過增加BloomFilter的層數(shù)來完成的。每增加一層,在查詢的時候就可能會遍歷多層BloomFilter來完成,每一層的容量都是上一層的兩倍(默認)。
2.2.3、BF.ADD
- 參數(shù)
- BF.ADD {key} {item}
- 功能
- 向key指定的Bloom中添加一個元素item。
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- item:待插入過濾器的元素;
- 返回值
- 元素不存在插入成功:返回1;
- 元素可能已經(jīng)存在:返回0;
- 其它情況返回相應的異常信息。
2.2.3、BF.MADD
- 參數(shù)
- BF.MADD {key} {item} [item…]
- 功能
- 向key指定的Bloom中添加多個元素item。
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- item:待插入過濾器的元素,可插入多個;
- 返回值
- 成功:返回一個數(shù)組,數(shù)組的每一個元素可能為1或0,當item一定不存在時數(shù)組元素值為1,當item可能已經(jīng)存在時數(shù)組元素值為0。
- 其它情況返回相應的異常信息。
2.2.5、BF.EXISTS
- 參數(shù)
- BF.EXISTS {key} {item}
- 功能
- 檢查一個元素是否可能存在于key指定的Bloom中
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- item:待檢查的元素;
- 返回值
- 元素一定不存在:0;
- 元素可能存在:1;
- 其它情況返回相應的異常信息。
2.2.6、BF.MEXISTS
- 參數(shù)
- BF.MEXISTS [item…]
- 功能
- 檢查多個元素是否可能存在于key指定的Bloom中
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- item:待檢查的元素,可設(shè)置多個;
- 返回值
- 成功:返回一個數(shù)組,數(shù)組的每一個元素可能為1或0,當item一定不存在時數(shù)組元素值為0,當item可能已經(jīng)存在時數(shù)組元素值為1。
- 其它情況返回相應的異常信息。
2.2.7、BF.INSERT
- 參數(shù)
- BF.INSERT {key} [CAPACITY {cap}] [ERROR {error}] [EXPANSION expansion] [NOCREATE] [NONSCALING] ITEMS {item…}
- 功能
- 向key指定的Bloom中添加多個元素,添加時可以指定大小和錯誤率,且可以控制在Bloom不存在的時候是否自動創(chuàng)建
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- CAPACITY:[如果過濾器已創(chuàng)建,則此參數(shù)將被忽略]。更多的信息參考<BF.RESERVE>;
- ERROR:[如果過濾器已創(chuàng)建,則此參數(shù)將被忽略]。更多的信息參考<BF.RESERVE>;
- expansion:布隆過濾器會自動創(chuàng)建一個子過濾器,子過濾器的大小是上一個過濾器大小乘以expansion。expansion的默認值是2,也就是說布隆過濾器擴容默認是2倍擴容。
- NOCREATE:如果設(shè)置了該參數(shù),當布隆過濾器不存在時則不會被創(chuàng)建。用于嚴格區(qū)分過濾器的創(chuàng)建和元素插入場景。該參數(shù)不能與CAPACITY和ERROR同時設(shè)置。
- NONSCALING:設(shè)置此項后,當添加到布隆過濾器中的數(shù)據(jù)達到初始容量后,不會擴容過濾器,并且會拋出異常((error) ERR non scaling filter is full)。
- ITEMS:待插入過濾器的元素列表,該參數(shù)必傳。
- 返回值
- 成功:返回一個數(shù)組,數(shù)組的每一個元素可能為1或0,當item一定不存在時數(shù)組元素值為1,當item可能已經(jīng)存在時數(shù)組元素值為0。
- 其它情況返回相應的異常信息。
2.2.8、BF.SCANDUMP
- 參數(shù)
- BF.SCANDUMP {key} {iter}
- 功能
- 對Bloom進行增量持久化操作(增量保存);
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:布隆過濾器的名字;
- iter:首次調(diào)用傳值0,或者上次調(diào)用此命令返回的結(jié)果值;
- 返回值
- 返回連續(xù)的(iter, data)對,直到(0,NULL),表示DUMP完成。
- 備注
2.2.9、BF.LOADCHUNK
- 參數(shù)
- BF.LOADCHUNK {key} {iter} {data}
- 功能
- 加載SCANDUMP持久化的Bloom數(shù)據(jù);
- 時間復雜度
- O(log N),N是過濾器的層數(shù)。
- 參數(shù)說明
- key:目標布隆過濾器的名字;
- iter:SCANDUMP返回的迭代器的值,和data一一對應;
- data:SCANDUMP返回的數(shù)據(jù)塊(data chunk);
- 返回值
- 成功則返回OK。
2.2.10、BF.INFO
- 參數(shù)
- BF.INFO {key}
- 功能
- 返回BloomFilter的相關(guān)信息;
- 時間復雜度
- O(1);
- 參數(shù)說明
- key:目標布隆過濾器的名字;
- 返回值
- Capacity:預設(shè)容量;
- Size:實際占用情況,但如何計算待進一步確認;
- Number of filters:過濾器層數(shù);
- Number of items inserted:已經(jīng)實際插入的元素數(shù)量;
- Expansion rate:子過濾器擴容系數(shù)(默認2);
2.2.11、BF.DEBUG
- 參數(shù)
- BF.DEBUG {key}
- 功能
- 查看BloomFilter的內(nèi)部詳細信息(如每層的元素個數(shù)、錯誤率等);
- 時間復雜度
- O(log N),N是過濾器的層數(shù);
- 參數(shù)說明
- key:目標布隆過濾器的名字;
- 返回值
- size:BloomFilter中已插入的元素數(shù)量;
- 每層BloomFilter的詳細信息
- bytes:占用字節(jié)數(shù)量;
- bits:占用bit位數(shù)量,bits = bytes * 8;
- hashes:該層hash函數(shù)數(shù)量;
- hashwidth:hash函數(shù)寬度;
- capacity:該層容量(第一層為BloomFilter初始化時設(shè)置的容量,第2層容量 = 第一層容量 * expansion,以此類推);
- size:該層中已插入的元素數(shù)量(各層size之和等于BloomFilter中已插入的元素數(shù)量size);
- ratio:該層錯誤率(第一層的錯誤率 = BloomFilter初始化時設(shè)置的錯誤率 * 0.5,第二層為第一層的0.5倍,以此類推,ratio與expansion無關(guān));
3、布隆過濾器的底層原理
3.1、布隆過濾器的底層結(jié)構(gòu)
??布隆過濾器本質(zhì)是一個巨大的bit數(shù)組(bit array)+幾個不同的無偏hash函數(shù)。
??布隆過濾器添加一個item(“zxiaofan”),其操作步驟是:
- 使用多個無偏哈希函數(shù)對item進行hash運算,得到多個hash值hash(zxiaofan);
- 每個hash值對bit數(shù)組取模得到位數(shù)組中的位置index(zxiaofan);
- 判斷所有index位是否都為1 ;
- 位都為1則說明該元素可能已經(jīng)存在了;
- 任意一位不為1則說明一定不存在,此時會將不為1的位置為1;
??需要注意的是,雖然使用了無偏hash函數(shù),使得hash值盡可能均勻,但是不同的item計算出的hash值依舊可能重復,所以布隆過濾器返回元素存在,實際是有可能不存在的。
取模運算(“Modulus Operation”)和取余運算(“Remainder Operation ”)兩個概念有重疊的部分但又不完全一致。主要的區(qū)別在于對負整數(shù)進行除法運算時操作不同。取模主要是用于計算機術(shù)語中。取余則更多是數(shù)學概念。a mod b = c,a、b符號一致時,取模、取余計算得出的C相同;a、b符號不一致時,取模計算的c其符號和b一致,取余計算的C其符號和a一致。
3.2、最佳hash函數(shù)數(shù)量與錯誤率的關(guān)系
??源碼中的hash函數(shù)數(shù)量計算公式:
# hash函數(shù)數(shù)量計算公式:# ceil(value):返回不小于value的最小整數(shù); # log(error):以10為底的對數(shù)函數(shù); # ln(x):以e為底的對數(shù)函數(shù); # 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; }??我們通過創(chuàng)建不同錯誤率不同容量的布隆過濾器,整理hash函數(shù)數(shù)量與錯誤率的關(guān)系。
# 公眾號@zxiaofan # 創(chuàng)建一個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# 創(chuàng)建一個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表示內(nèi)部使用的hash函數(shù)數(shù)量; 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"??由上面的執(zhí)行結(jié)果可以看出,Redis布隆過濾器中最佳hash函數(shù)數(shù)量與錯誤率的關(guān)系如下:
| 0.1 | 5 |
| 0.01 | 8 |
| 0.001 | 11 |
| 0.0001 | 15 |
| 0.00001 | 18 |
| 0.000001 | 21 |
| 0.0000001 | 25 |
3.3、所需存儲空間與錯誤率及容量關(guān)系
??通過創(chuàng)建不同錯誤率不同容量的布隆過濾器,整理存儲空間與錯誤率及容量的關(guān)系。
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。| 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 |
??占用內(nèi)存(單位M) = bytes值/1024/1024。
??從上述對比分析可以看出,錯誤率{error_rate}越小,所需的存儲空間越大; 初始化設(shè)置的元素數(shù)量{capacity}越大,所需的存儲空間越大,當然如果實際遠多于預設(shè)時,準確率就會降低。
??在1千萬數(shù)據(jù)場景下,error_rate為0.001、0.0001、0.00001實際占用內(nèi)存都是30M以下,此時如果對準確性要求高,初始化時將錯誤率設(shè)置低一點是完全無傷大雅的。
??RedisBloom官方默認的error_rate是 0.01,默認的capacity是 100,源碼如下:
// RedisBloom/src/rebloom.cstatic double BFDefaultErrorRate = 0.01; static size_t BFDefaultInitCapacity = 100;3.4、布隆過濾器如何擴容
??先執(zhí)行幾行命令,看看實際效果。
# 公眾號 @zxiaofan # 創(chuàng)建一個容量為5的BloomFilter,其key為“bfexp”; 127.0.0.1:6379> bf.reserve bfexp 0.1 5 OK# 查看BloomFilter的內(nèi)部信息,此時BloomFilter的層數(shù)為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的層數(shù)變?yōu)?; # BloomFilter的元素數(shù)量為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擴容閾值,層數(shù)依舊為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的層數(shù)變?yōu)?; 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"BloomFilter擴容邏輯:
- 插入m個元素,計算實際插入BloomFilter的元素數(shù)量;
- 如果實際插入元素數(shù)量 > BloomFilter的容量,則觸發(fā)擴容;
- 擴容的倍數(shù)為BloomFilter初始化時設(shè)置的expansion(默認2);
BloomFilter擴容注意事項:
- 擴容觸發(fā)的條件是 實際插入 > 容量,實際插入數(shù)量 = 容量時,是不會觸發(fā)擴容的;
- 實際插入指的是插入成功,即使計劃插入的數(shù)據(jù)過濾器中沒有,但由于hash沖突導入插入失敗,這種也不算實際插入成功。假設(shè)容量是20,如果插入21個元素,但由于重復甚至于hash沖突,導致實際插入的數(shù)量不足21個,此時也不會觸發(fā)擴容;
4、布隆過濾器有哪些應用場景
4.1、郵件黑名單&網(wǎng)站黑名單
??郵箱地址數(shù)十億計且長度不固定,我們需要從海量的郵箱地址中識別出垃圾郵箱地址。當一個郵箱地址被判定為垃圾郵箱后,就將此地址添加進布隆過濾器中即可。
??同理,萬維網(wǎng)上的URL地址中包含了大量的非法或惡意URL,利用布隆過濾器也可以快速判斷此類URL。當布隆過濾器返回結(jié)果為存在時,才對URL進行進一步判定處理。
4.2、新聞推薦去重
??對于百度新聞、頭條新聞等信息推薦平臺,為了盡可能提升用戶體驗,應最大可能保證推薦給用戶的新聞不重復,將已推薦給用戶的文章ID存入布隆過濾器,再次推薦時先判斷是否已推送即可。
4.3、緩存穿透&惡意攻擊
??緩存穿透:是指查詢了緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù)。當此類查詢請求量過大時(比如系統(tǒng)被惡意攻擊),緩存系統(tǒng)或數(shù)據(jù)庫的壓力將增大,極容易宕機。
??方式1:當查詢DB中發(fā)現(xiàn)某數(shù)據(jù)不存在時,則將此數(shù)據(jù)ID存入布隆過濾器,每次查詢時先判斷是否存在于布隆過濾器,存在則說明數(shù)據(jù)庫無此數(shù)據(jù),無需繼續(xù)查詢了。當然此種方式僅能處理同一個ID重復訪問的場景。
??方式2:如果攻擊者惡意構(gòu)造了大量不重復的且數(shù)據(jù)庫中不存在的數(shù)據(jù)呢,此時可將數(shù)據(jù)庫中已有的數(shù)據(jù)的唯一ID放入布隆過濾器,每次查詢時先判斷是否存在于布隆過濾器,存在才調(diào)用后端系統(tǒng)查詢,則可有效過濾惡意攻擊。
??使用方式1需要防止指定ID最初不存在于DB中,遂將此ID存入“數(shù)據(jù)不存在的過濾器”中,但后續(xù)DB又新增了此ID,因為布隆過濾器不支持刪除操作,一旦發(fā)生此類場景,就肯定會出現(xiàn)誤判了。
??使用方式2需要注意數(shù)據(jù)的增量,避免數(shù)據(jù)庫中新增了數(shù)據(jù)而過濾器中還沒有導致無法查詢到數(shù)據(jù)。當然如果此時DB中刪除了指定數(shù)據(jù),布隆過濾器是無法隨之刪除指紋標記的。
??了解了原理方能如臂使指。此外建議,生產(chǎn)數(shù)據(jù)的ID應定義生成規(guī)則及校驗規(guī)則(比如身份證的最后一位就是校驗位),這樣每次查詢先判斷這個ID是否有效,有效才進行后續(xù)的步驟,這樣可以充分過濾外部的惡意攻擊。
4.4、網(wǎng)頁爬蟲URL去重
??網(wǎng)絡(luò)爬蟲是一個自動提取網(wǎng)頁的程序,它為搜索引擎從萬維網(wǎng)上下載網(wǎng)頁,是搜索引擎的重要組成。傳統(tǒng)爬蟲從一個或若干初始網(wǎng)頁的URL開始,獲得初始網(wǎng)頁上的URL,在抓取網(wǎng)頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統(tǒng)的一定停止條件。由于網(wǎng)站之間存在互相引用,抓取的URL可能存在重復,為了避免爬取重復的數(shù)據(jù),可以將已爬取的URL放入布隆過濾器中,每次爬取新URL時先做判斷。
4.5、查詢加速
??Google BigTable、Apache HBase、Apache Cassandra、Postgresql 等Key-Value存儲系統(tǒng),使用布隆過濾器確定數(shù)據(jù)是否存在,從而減少代價相對較高的磁盤查詢。
??在HBase中,一個HFile一旦被寫完就只會被查詢不會被更新。將文件的所有key進行計算,生成這個文件的布隆過濾器,并將其寫入到元數(shù)據(jù)中,以后所有對該文件的查詢都會先查詢對應的布隆過濾器,如果在布隆過濾器中不存在則不需要訪問該文件,節(jié)省了大量的對磁盤的低速訪問。
??Cassandra原理類似,采用了追加而不是修改的方式來處理數(shù)據(jù)文件。一塊完整的數(shù)據(jù)被dump到文件后就不會再被更新。在每個文件被dump到硬盤上時,都會對該文件生成一個布隆過濾器,而該布隆過濾器會被存放到內(nèi)存中。所有對該文件的訪問都會先訪問對應的布隆過濾器,如果布隆過濾器返回不存在則無需訪問硬盤上的文件。從而大大提高了查詢的效率。
4.6、防止重復請求
??第一次請求,將請求參數(shù)放入布隆過濾器中,第二次請求時,先判斷請求參數(shù)是否存在于BloomFilter中。
4.7、區(qū)塊鏈應用
??區(qū)塊鏈中使用布隆過濾器來加快錢包同步;以太坊使用布隆過濾器用于快速查詢以太坊區(qū)塊鏈的日志。
??比特幣錢包如何知道有多少錢(比特幣錢包如何知道有多少UTXO),比特幣系統(tǒng)沒有余額的概念,它使用的是UTXO模型(Unspent Transaction Outputs,未使用過的交易輸出)。比特幣每一筆交易記錄了時間、發(fā)送人、接收人和金額。那如果要計算A的余額,那么就要遍歷所有跟A有關(guān)的交易,減去A發(fā)送的每一筆金額,并加上A接收的每一筆金額。
??輕客戶端下載完整的區(qū)塊鏈賬本自己查詢,這顯然是不現(xiàn)實的,如果輕客戶端告訴全節(jié)點自己的錢包地址,則又泄漏了隱私。現(xiàn)有的實現(xiàn)方式是,錢包節(jié)點以布隆過濾器的方式告訴全節(jié)點自己的錢包地址,全節(jié)點返回可能相關(guān)的UTXO。
??以太坊記錄交易日志也采用了布隆過濾器,以太坊的每個區(qū)塊頭包含當前區(qū)塊中所有收據(jù)的日志的布隆過濾器logsBloom,便于高效查詢?nèi)罩緮?shù)據(jù)。
??數(shù)學改變生活。
5、布隆過濾器的優(yōu)缺點
5.1、布隆過濾器的優(yōu)勢
- 【適合大數(shù)據(jù)場景】:支持海量數(shù)據(jù)場景下高效判斷元素是否存在;
- 【節(jié)省空間】:不存儲數(shù)據(jù)本身,僅存儲hash結(jié)果取模運算后的位標記;
- 【數(shù)據(jù)保密】:不存儲數(shù)據(jù)本身,適合某些保密場景;
5.2、布隆過濾器的缺點
- 【誤判】:由于存在hash碰撞,匹配結(jié)果如果是“存在于過濾器中”,實際不一定存在;
- 【不可刪除】:沒有存儲元素本身,所以只能添加但不可刪除;
- 【空間利用率不高】:創(chuàng)建過濾器時需提前預估創(chuàng)建,當錯誤率越低時,為了盡可能避免hash碰撞,冗余的空間就越多;需要注意的是,空間利用率不高和節(jié)省空間并不沖突;
- 【容量滿時誤報率增加】當容量快滿時,hash碰撞的概率變大,插入、查詢的錯誤率也就隨之增加了。
5.3、布隆過濾器其他問題
- 【不支持計數(shù)】:同一個元素可以多次插入,但效果和插入一次相同;
- 【查詢速度受錯誤率影響】:由于錯誤率影響hash函數(shù)的數(shù)量,當hash函數(shù)越多,每次插入、查詢需做的hash操作就越多;
6、延伸拓展
6.1、超大規(guī)模布隆過濾器如何處理
??除自建Redis外,阿里云-云數(shù)據(jù)庫Redis是又一不錯的選擇,即買即用。但需要注意的是,阿里云的社區(qū)版主從版Redis單機支持10W QPS,如果數(shù)據(jù)量過大,需要遷移到集群版;4096GB集群性能增強版最大支持6KW QPS。
??面對超大規(guī)模數(shù)據(jù),除了使用更大規(guī)格的集群版Redis,我們是否還有其他解決方式呢?結(jié)合前人的優(yōu)秀思路(Oracle大型機轉(zhuǎn)為分布式MySQL集群),拆分key也一個不錯的思路,即讓key均勻分散到不同的小集群中。
??回到我們的問題,如果我們需要校驗的數(shù)據(jù)量超大,比如搜索引擎的爬蟲需要判重URL,使用一個布隆過濾器性能肯定受影響。那么我們可以 取Hash(URL)的前幾位 作為不同布隆過濾器的標記,此時URL就將均勻的分布到不同的布隆過濾器中。
【玩轉(zhuǎn)Redis系列文章 近期精選 @zxiaofan】
《玩轉(zhuǎn)Redis-HyperLogLog原理探索》
《玩轉(zhuǎn)Redis-HyperLogLog統(tǒng)計微博日活月活》
《玩轉(zhuǎn)Redis-京東簽到領(lǐng)京豆如何實現(xiàn)》
《玩轉(zhuǎn)Redis-老板帶你深入理解分布式鎖》
《玩轉(zhuǎn)Redis-如何高效訪問Redis中的海量數(shù)據(jù)》
總結(jié)
以上是生活随笔為你收集整理的Redis中布隆过滤器的使用及原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ubuntu16.04安装,使用redi
- 下一篇: mysql之慢查询详解,mysqldum