日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis中布隆过滤器的使用及原理

發(fā)布時間:2024/2/28 数据库 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis中布隆过滤器的使用及原理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

《玩轉(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(查詢多個元素)

命令功能參數(shù)
BF.RESERVE創(chuàng)建一個大小為capacity,錯誤率為error_rate的空的BloomBF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion] [NONSCALING]
BF.ADD向key指定的Bloom中添加一個元素itemBF.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來完成,每一層的容量都是上一層的兩倍(默認)。
# 公眾號@zxiaofan # 創(chuàng)建一個容量為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

  • 參數(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。
    • 其它情況返回相應的異常信息。
# 公眾號@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

  • 參數(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。
    • 其它情況返回相應的異常信息。
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# 這里有個比較有意思的現(xiàn)象,item5未顯示添加成功,但是后續(xù)卻顯示exists # 這說明發(fā)生了hash沖突,誤判就是這樣產(chǎn)生的。

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完成。
  • 備注
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

  • 參數(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。
# 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

  • 參數(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);
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

  • 參數(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));
# 公眾號 @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 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"

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)系如下:

錯誤率{error_rate}hash函數(shù)的最佳數(shù)量
0.15
0.018
0.00111
0.000115
0.0000118
0.00000121
0.000000125

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。 錯誤率{error_rate}元素數(shù)量{capacity}占用內(nèi)存(單位M)
0.00110萬0.19
0.0011百萬1.89
0.0011千萬18.9
0.0011億188.6
0.000110萬0.25
0.00011百萬2.5
0.00011千萬24.6
0.00011億245.7
0.0000110萬0.3
0.000011百萬3.01
0.000011千萬30.1
0.000011億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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。