一. Bloom Filte介紹
1. 含義
(1). 布隆過濾器(Bloom Filter)是由Howard Bloom在1970年提出的一種比較巧妙的概率型數據結構,它實際上是由一個很長的二進制(0或1)向量和一系列隨機映射函數組成。
(2). 布隆過濾器可以用於檢索一個元素是否在一個集合中。它可以告訴你某種東西一定不存在或者可能存在。當布隆過濾器說,某種東西存在時,這種東西可能不存在;當布隆過濾器說,某種東西不存在時,那么這種東西一定不存在。
(3). 布隆過濾器 優點:A. 空間效率高,占用空間少 B. 查詢時間短
缺點:A. 有一定的誤判率 B. 元素不能刪除
2. 原理
當一個元素被加入集合時,通過K個散列函數將這個元素映射成一個位數組中的K個點(使用多個哈希函數對元素key (bloom中不存value) 進行哈希,算出一個整數索引值,然后對位數組長度進行取模運算得到一個位置,每個無偏哈希函數都會得到一個不同的位置),把它們置為1。
檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:① 如果這些點有任何一個為0(如下圖的e),則被檢元素一定不在;如果都是1(如下圖的d),並不能完全說明這個元素就一定存在其中,有可能這些位置為1是因為其他元素的存在,這就是布隆過濾器會出現誤判的原因。
如下圖:
補充:
Bloom Filter跟 ‘’單哈希函數BitMap‘’ 不同之處在於:Bloom Filter使用了k個哈希函數,每個字符串跟k個bit對應,從而降低了沖突的概率。
3. 實現
(1). Redis的bitmap
基於redis的 bitmap數據結構 的相關指令來執行。
(2). RedisBloom (推薦)
布隆過濾器可以使用Redis中的位圖(bitmap)操作實現,直到Redis4.0版本提供了插件功能,Redis官方提供的布隆過濾器才正式登場,布隆過濾器作為一個插件加載到Redis Server中,官網推薦了一個 RedisBloom 作為 Redis 布隆過濾器的 Module。
詳細安裝、指令操作參考:https://github.com/RedisBloom/RedisBloom
文檔地址:https://oss.redislabs.com/redisbloom/
(3). PyreBloom
(4). Lua腳本實現
詳見:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
(5). guvua包自帶的布隆過濾器
防止緩存穿透的業務偽代碼分享:
import com.google.common.hash.BloomFilter; //初始化布隆過濾器 //1000:期望存入的數據個數,0.001:期望的誤差率 BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf‐8")), 1000, 0.001); //把所有數據存入布隆過濾器 void init(){ for (String key: keys) { bloomFilter.put(key); } } String get(String key) { // 從布隆過濾器這一級緩存判斷下key是否存在 Boolean exist = bloomFilter.mightContain(key); if(!exist){ return ""; } // 從緩存中獲取數據 String cacheValue = cache.get(key); // 緩存為空 if (StringUtils.isBlank(cacheValue)) { // 從存儲中獲取 String storageValue = storage.get(key); cache.set(key, storageValue); // 如果存儲數據為空, 需要設置一個過期時間(300秒) if (storageValue == null) { cache.expire(key, 60 * 5); } return storageValue; } else { // 緩存非空 return cacheValue; } }
二. RedisBloom實操
1. 簡介
RedisBloom模塊提供了四種數據類型:Bloom Filter (布隆過濾器)、Cuckoo Filter(布谷鳥過濾器)、 Count-Mins-Sketch、 Top-K 。 Bloom Filter和 Cuckoo 用於確定(以給定的確定性)集合中是否存在某項。使用 Count-Min Sketch 來估算子線性空間中的項目數,使用Top-K 維護K個最頻繁項目的列表,本節重點介紹 Bloom Filter的使用。
參考官網:https://github.com/RedisBloom/RedisBloom
https://oss.redislabs.com/redisbloom/
連接RedisBloom的客戶端如下,其中.Net客戶端是 StackExchange.Redis的一個擴展類(https://gist.github.com/naile/96de4e9548c7b5fd6c0614009ffec755),源碼的本質就是 Excute 執行RedisBloom指令 的封裝了一下而已。
2. 安裝
(1). 基於docker安裝
#1. 下載最新版本的鏡像
docker pull redislabs/rebloom:latest
#2. 發布成容器,容器名為 redis-redisbloom docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
#3. 進入容器內部 docker exec -it redis-redisbloom bash # redis-cli # 127.0.0.1:6379> bf.add tiancheng hello
(2). 直接編譯安裝
下載地址:https://redislabs.com/redis-enterprise-software/download-center/modules/?_ga=2.248871111.1352755929.1604741675-1478122061.1604741675
git clone https://github.com/RedisBloom/RedisBloom.git cd RedisBloom make //編譯 會生成一個rebloom.so文件 redis-server --loadmodule /path/to/rebloom.so redis-cli -h 127.0.0.1 -p 6379
3. 指令介紹和實操
(1). 指令
#1.添加單個元素到布隆過濾器
bf.add filterName key
#2. 判斷單個元素是否在布隆過濾器匯總 bf.exists filterName key
#3. 添加多個元素到布隆過濾器 bf.madd filterName key1 key2 key3
#4. 判斷多個元素是否在布隆過濾器 bf.mexists filterName key1 key2 key3
#高級用法(配置期望錯誤率 和初始容量)
#上述bf.add命令如果過濾器沒有創建的時候,會自動創建,並且使用的默認參數
# bf.reserve指令,創建一個自定義過濾器,有三個參數, 創建完成后,再執行bf.add指令
# filterName: 過濾器名稱
# error_rate: 期望錯誤率,期望錯誤率越低,需要的空間就越大
# capacity:初始容量,當實際元素的數量超過這個初始容量的時候,誤判率會上升。
eg:
bf.reserve ypfFilter1 0.0001 1000000
PS:如果對應的key已經存在時,在執行bf.reserve
命令就會報錯。如果不使用bf.reserve
命令創建,而是使用Redis自動創建的布隆過濾器,默認的error_rate
是 0.01,capacity
是 100。
布隆過濾器的error_rate
越小,需要的存儲空間就越大,對於不需要過於精確的場景,error_rate
設置稍大一點也可以。布隆過濾器的capacity
設置的過大,會浪費存儲空間,設置的過小,就會影響准確率,所以在使用之前一定要盡可能地精確估計好元素數量,還需要加上一定的冗余空間以避免實際元素可能會意外高出設置值很多。總之,error_rate
和 capacity
都需要設置一個合適的數值。
(2). 實操
待補充。。。
三. 應用場景
1. 解決緩存穿透
(1). 含義
業務請求中數據緩存中沒有,DB中也沒有,導致類似請求直接跨過緩存,反復在DB中查詢,與此同時緩存也不會得到更新。(詳見:https://www.cnblogs.com/yaopengfei/p/13878124.html)
(2). 解決思路
事先把存在的key都放到redis的Bloom Filter 中,他的用途就是存在性檢測,如果 BloomFilter 中不存在,那么數據一定不存在;如果 BloomFilter 中存在,實際數據也有可能會不存在。
剖析:布隆過濾器可能會誤判,放過部分請求,當不影響整體,所以目前該方案是處理此類問題最佳方案。
2. 黑名單校驗
識別垃圾郵件,只要發送者在黑名單中的,就識別為垃圾郵件。假設黑名單的數量是數以億計的,存放起來就是非常耗費存儲空間的,布隆過濾器則是一個較好的解決方案。把所有黑名單都放在布隆過濾器中,再收到郵件時,判斷郵件地址是否在布隆過濾器中即可。
ps:
如果用哈希表,每存儲一億個 email地址,就需要 1.6GB的內存(用哈希表實現的具體辦法是將每一個 email地址對應成一個八字節的信息指紋,然后將這些信息指紋存入哈希表,由於哈希表的存儲效率一般只有 50%,因此一個 email地址需要占用十六個字節。一億個地址大約要 1.6GB,即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百 GB的內存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解決同樣的問題。
3. Web攔截器
(1). 含義
如果相同請求則攔截,防止重復被攻擊。
(2). 解決思路
用戶第一次請求,將請求參數放入布隆過濾器中,當第二次請求時,先判斷請求參數是否被布隆過濾器命中,從而提高緩存命中率。
參考文章:https://github.com/luw2007/bloomfilter/blob/master/doc/bloomfilter_in_action.md
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。