玩轉Redis-Redis中布隆過濾器的使用及原理
《玩轉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) (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 bfinsert
1) Capacity
2) (integer) 5
3) Size
4) (integer) 155
5) Number of filters
6)