redis005--布隆過(guò)濾器

上一節(jié)我們學(xué)會(huì)了使用 HyperLogLog 數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行估數(shù)梗摇,它非常有價(jià)值违柏,可以解決多精確度不高的統(tǒng)計(jì)需求。

但是如果我們想知道某一個(gè)值是不是已經(jīng)在 HyperLogLog 結(jié)構(gòu)里面了亮蛔,它就無(wú)能為力了宠互,它只提供了 pfadd 和 pfcount 方法畏线,沒(méi)有提供 pfcontains 這種方法。

講個(gè)使用場(chǎng)景良价,比如我們?cè)谑褂眯侣効蛻?hù)端看新聞時(shí)寝殴,它會(huì)給我們不停地推薦新的內(nèi)容蒿叠,它每次推薦時(shí)要去重,去掉那些已經(jīng)看過(guò)的內(nèi)容蚣常。問(wèn)題來(lái)了市咽,新聞客戶(hù)端推薦系統(tǒng)如何實(shí)現(xiàn)推送去重?

你會(huì)想到服務(wù)器記錄了用戶(hù)看過(guò)的所有歷史記錄抵蚊,當(dāng)推薦系統(tǒng)推薦新聞時(shí)會(huì)從每個(gè)用戶(hù)的歷史記錄里進(jìn)行篩選施绎,過(guò)濾掉那些已經(jīng)存在的記錄。問(wèn)題是當(dāng)用戶(hù)量很大贞绳,每個(gè)用戶(hù)看過(guò)的新聞?dòng)趾芏嗟那闆r下谷醉,這種方式,推薦系統(tǒng)的去重工作在性能上跟的上么冈闭?


實(shí)際上,如果歷史記錄存儲(chǔ)在關(guān)系數(shù)據(jù)庫(kù)里,去重就需要頻繁地對(duì)數(shù)據(jù)庫(kù)進(jìn)行 exists 查詢(xún)窘俺,當(dāng)系統(tǒng)并發(fā)量很高時(shí)是掰,數(shù)據(jù)庫(kù)是很難扛住壓力的。

你可能又想到了緩存耍休,但是如此多的歷史記錄全部緩存起來(lái)刃永,那得浪費(fèi)多大存儲(chǔ)空間啊羊精?而且這個(gè)存儲(chǔ)空間是隨著時(shí)間線性增長(zhǎng)斯够,你撐得住一個(gè)月,你能撐得住幾年么园匹?但是不緩存的話雳刺,性能又跟不上,這該怎么辦裸违?

這時(shí)掖桦,布隆過(guò)濾器 (Bloom Filter) 閃亮登場(chǎng)了,它就是專(zhuān)門(mén)用來(lái)解決這種去重問(wèn)題的供汛。它在起到去重的同時(shí)枪汪,在空間上還能節(jié)省 90% 以上,只是稍微有那么點(diǎn)不精確怔昨,也就是有一定的誤判概率雀久。

布隆過(guò)濾器是什么 ?

布隆過(guò)濾器可以理解為一個(gè)不怎么精確的 set 結(jié)構(gòu)趁舀,當(dāng)你使用它的 contains 方法判斷某個(gè)對(duì)象是否存在時(shí)赖捌,它可能會(huì)誤判。但是布隆過(guò)濾器也不是特別不精確矮烹,只要參數(shù)設(shè)置的合理越庇,它的精確度可以控制的相對(duì)足夠精確罩锐,只會(huì)有小小的誤判概率。

當(dāng)布隆過(guò)濾器說(shuō)某個(gè)值存在時(shí)卤唉,這個(gè)值可能不存在涩惑;當(dāng)它說(shuō)不存在時(shí),那就肯定不存在桑驱。打個(gè)比方竭恬,當(dāng)它說(shuō)不認(rèn)識(shí)你時(shí),肯定就不認(rèn)識(shí)熬的;當(dāng)它說(shuō)見(jiàn)過(guò)你時(shí)痊硕,可能根本就沒(méi)見(jiàn)過(guò)面,不過(guò)因?yàn)槟愕哪樃J(rèn)識(shí)的人中某臉比較相似 (某些熟臉的系數(shù)組合)悦析,所以誤判以前見(jiàn)過(guò)你寿桨。

套在上面的使用場(chǎng)景中,布隆過(guò)濾器能準(zhǔn)確過(guò)濾掉那些已經(jīng)看過(guò)的內(nèi)容强戴,那些沒(méi)有看過(guò)的新內(nèi)容亭螟,它也會(huì)過(guò)濾掉極小一部分 (誤判),但是絕大多數(shù)新內(nèi)容它都能準(zhǔn)確識(shí)別骑歹。這樣就可以完全保證推薦給用戶(hù)的內(nèi)容都是無(wú)重復(fù)的预烙。

布隆過(guò)濾器基本使用

布隆過(guò)濾器有二個(gè)基本指令,bf.add 添加元素道媚,bf.exists 查詢(xún)?cè)厥欠翊嬖诒獾В挠梅ê?set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一個(gè)元素最域,如果想要一次添加多個(gè)谴分,就需要用到 bf.madd 指令。同樣如果需要一次查詢(xún)多個(gè)元素是否存在镀脂,就需要用到bf.mexists 指令牺蹄。

使用的布隆過(guò)濾器只是默認(rèn)參數(shù)的布隆過(guò)濾器,它在我們第一次 add 的時(shí)候自動(dòng)創(chuàng)建薄翅。Redis 其實(shí)還提供了自定義參數(shù)的布隆過(guò)濾器沙兰,需要我們?cè)?add 之前使用 bf.reserve指令顯式創(chuàng)建。如果對(duì)應(yīng)的 key 已經(jīng)存在翘魄,bf.reserve 會(huì)報(bào)錯(cuò)鼎天。bf.reserve 有三個(gè)參數(shù),分別是 key, error_rate 和 initial_size暑竟。錯(cuò)誤率越低斋射,需要的空間越大。initial_size 參數(shù)表示預(yù)計(jì)放入的元素?cái)?shù)量,當(dāng)實(shí)際數(shù)量超出這個(gè)數(shù)值時(shí)绩鸣,誤判率會(huì)上升怀大。

所以需要提前設(shè)置一個(gè)較大的數(shù)值避免超出導(dǎo)致誤判率升高。如果不使用 bf.reserve呀闻,默認(rèn)的 error_rate 是 0.01,默認(rèn)的 initial_size 是 100潜慎。

注意事項(xiàng)

布隆過(guò)濾器的 initial_size 估計(jì)的過(guò)大捡多,會(huì)浪費(fèi)存儲(chǔ)空間,估計(jì)的過(guò)小铐炫,就會(huì)影響準(zhǔn)確率垒手,用戶(hù)在使用之前一定要盡可能地精確估計(jì)好元素?cái)?shù)量,還需要加上一定的冗余空間以避免實(shí)際元素可能會(huì)意外高出估計(jì)值很多倒信。

布隆過(guò)濾器的 error_rate 越小科贬,需要的存儲(chǔ)空間就越大,對(duì)于不需要過(guò)于精確的場(chǎng)合鳖悠,error_rate 設(shè)置稍大一點(diǎn)也無(wú)傷大雅榜掌。比如在新聞去重上而言,誤判率高一點(diǎn)只會(huì)讓小部分文章不能讓合適的人看到乘综,文章的整體閱讀量不會(huì)因?yàn)檫@點(diǎn)誤判率就帶來(lái)巨大的改變憎账。

布隆過(guò)濾器基本使用

布隆過(guò)濾器有二個(gè)基本指令,bf.add 添加元素卡辰,bf.exists 查詢(xún)?cè)厥欠翊嬖诎澹挠梅ê?set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一個(gè)元素九妈,如果想要一次添加多個(gè)反砌,就需要用到 bf.madd 指令。同樣如果需要一次查詢(xún)多個(gè)元素是否存在萌朱,就需要用到 bf.mexists 指令宴树。

127.0.0.1:6379> bf.add codehole user1
(integer) 1
127.0.0.1:6379> bf.add codehole user2
(integer) 1
127.0.0.1:6379> bf.add codehole user3
(integer) 1
127.0.0.1:6379> bf.exists codehole user1
(integer) 1
127.0.0.1:6379> bf.exists codehole user2
(integer) 1

布隆過(guò)濾器的原理

學(xué)會(huì)了布隆過(guò)濾器的使用,下面有必要把原理解釋一下嚷兔,不然讀者還會(huì)繼續(xù)蒙在鼓里森渐。


原理

每個(gè)布隆過(guò)濾器對(duì)應(yīng)到 Redis 的數(shù)據(jù)結(jié)構(gòu)里面就是一個(gè)大型的位數(shù)組和幾個(gè)不一樣的無(wú)偏 hash 函數(shù)。所謂無(wú)偏就是能夠把元素的 hash 值算得比較均勻冒晰。向布隆過(guò)濾器中添加 key 時(shí)同衣,會(huì)使用多個(gè) hash 函數(shù)對(duì) key 進(jìn)行 hash 算得一個(gè)整數(shù)索引值然后對(duì)位數(shù)組長(zhǎng)度進(jìn)行取模運(yùn)算得到一個(gè)位置,每個(gè) hash 函數(shù)都會(huì)算得一個(gè)不同的位置壶运。再把位數(shù)組的這幾個(gè)位置都置為 1 就完成了 add 操作耐齐。

向布隆過(guò)濾器詢(xún)問(wèn) key 是否存在時(shí),跟 add 一樣,也會(huì)把 hash 的幾個(gè)位置都算出來(lái)埠况,看看位數(shù)組中這幾個(gè)位置是否都位 1耸携,只要有一個(gè)位為 0,那么說(shuō)明布隆過(guò)濾器中這個(gè)key 不存在辕翰。如果都是 1夺衍,這并不能說(shuō)明這個(gè) key 就一定存在,只是極有可能存在喜命,因?yàn)檫@些位被置為 1 可能是因?yàn)槠渌?key 存在所致沟沙。如果這個(gè)位數(shù)組比較稀疏,這個(gè)概率就會(huì)很大壁榕,如果這個(gè)位數(shù)組比較擁擠矛紫,這個(gè)概率就會(huì)降低。

空間占用估計(jì)

布隆過(guò)濾器的空間占用有一個(gè)簡(jiǎn)單的計(jì)算公式牌里。布隆過(guò)濾器有兩個(gè)參數(shù)颊咬,第一個(gè)是預(yù)計(jì)元素的數(shù)量 n,第二個(gè)是錯(cuò)誤率 f牡辽。公式根據(jù)這兩個(gè)輸入得到兩個(gè)輸出喳篇,第一個(gè)輸出是位數(shù)組的長(zhǎng)度 l,也就是需要的存儲(chǔ)空間大小 (bit)催享,第二個(gè)輸出是 hash 函數(shù)的最佳數(shù)量 k杭隙。hash 函數(shù)的數(shù)量也會(huì)直接影響到錯(cuò)誤率,最佳的數(shù)量會(huì)有最低的錯(cuò)誤率因妙。
k=0.7*(l/n) # 約等于
f=0.6185^(l/n) # ^ 表示次方計(jì)算痰憎,也就是 math.pow

從公式中可以看出
1、位數(shù)組相對(duì)越長(zhǎng) (l/n)攀涵,錯(cuò)誤率 f 越低铣耘,這個(gè)和直觀上理解是一致的
2、位數(shù)組相對(duì)越長(zhǎng) (l/n)以故,hash 函數(shù)需要的最佳數(shù)量也越多蜗细,影響計(jì)算效率
3、當(dāng)一個(gè)元素平均需要 1 個(gè)字節(jié) (8bit) 的指紋空間時(shí) (l/n=8)怒详,錯(cuò)誤率大約為 2%
4炉媒、錯(cuò)誤率為 10%,一個(gè)元素需要的平均指紋空間為 4.792 個(gè) bit昆烁,大約為 5bit
5吊骤、錯(cuò)誤率為 1%,一個(gè)元素需要的平均指紋空間為 9.585 個(gè) bit静尼,大約為 10bit
6白粉、錯(cuò)誤率為 0.1%传泊,一個(gè)元素需要的平均指紋空間為 14.377 個(gè) bit,大約為 15bit
你也許會(huì)想鸭巴,如果一個(gè)元素需要占據(jù) 15 個(gè) bit眷细,那相對(duì) set 集合的空間優(yōu)勢(shì)是不是就沒(méi)有那么明顯了?這里需要明確的是鹃祖,set 中會(huì)存儲(chǔ)每個(gè)元素的內(nèi)容溪椎,而布隆過(guò)濾器僅僅存儲(chǔ)元素的指紋。元素的內(nèi)容大小就是字符串的長(zhǎng)度惯豆,它一般會(huì)有多個(gè)字節(jié)池磁,甚至是幾十個(gè)上百個(gè)字節(jié),每個(gè)元素本身還需要一個(gè)指針被 set 集合來(lái)引用楷兽,這個(gè)指針又會(huì)占去 4 個(gè)字節(jié)或 8 個(gè)字節(jié),取決于系統(tǒng)是 32bit 還是 64bit华临。而指紋空間只有接近 2 個(gè)字節(jié)芯杀,所以布隆過(guò)濾器的空間優(yōu)勢(shì)還是非常明顯的。

實(shí)際元素超出時(shí) 雅潭,誤判率會(huì)怎樣變化

當(dāng)實(shí)際元素超出預(yù)計(jì)元素時(shí)揭厚,錯(cuò)誤率會(huì)有多大變化,它會(huì)急劇上升么扶供,還是平緩地上升筛圆,這就需要另外一個(gè)公式,引入?yún)?shù) t 表示實(shí)際元素和預(yù)計(jì)元素的倍數(shù) t
f=(1-0.5t)k # 極限近似椿浓,k 是 hash 函數(shù)的最佳數(shù)量
當(dāng) t 增大時(shí)太援,錯(cuò)誤率,f 也會(huì)跟著增大扳碍,分別選擇錯(cuò)誤率為 10%,1%,0.1% 的 k 值提岔,畫(huà)出它的曲線進(jìn)行直觀觀察。


從這個(gè)圖中可以看出曲線還是比較陡峭的
1笋敞、錯(cuò)誤率為 10% 時(shí)碱蒙,倍數(shù)比為 2 時(shí),錯(cuò)誤率就會(huì)升至接近 40%夯巷,這個(gè)就比較危險(xiǎn)了
2赛惩、錯(cuò)誤率為 1% 時(shí),倍數(shù)比為 2 時(shí)趁餐,錯(cuò)誤率升至 15%喷兼,也挺可怕的
3、錯(cuò)誤率為 0.1%澎怒,倍數(shù)比為 2 時(shí)褒搔,錯(cuò)誤率升至 5%阶牍,也比較懸了

布隆過(guò)濾器的其它應(yīng)用

在爬蟲(chóng)系統(tǒng)中,我們需要對(duì) URL 進(jìn)行去重星瘾,已經(jīng)爬過(guò)的網(wǎng)頁(yè)就可以不用爬了走孽。但是URL 太多了,幾千萬(wàn)幾個(gè)億琳状,如果用一個(gè)集合裝下這些 URL 地址那是非常浪費(fèi)空間的磕瓷。這時(shí)候就可以考慮使用布隆過(guò)濾器。它可以大幅降低去重存儲(chǔ)消耗念逞,只不過(guò)也會(huì)使得爬蟲(chóng)系統(tǒng)錯(cuò)過(guò)少量的頁(yè)面困食。

布隆過(guò)濾器在 NoSQL 數(shù)據(jù)庫(kù)領(lǐng)域使用非常廣泛,我們平時(shí)用到的 HBase翎承、Cassandra還有 LevelDB硕盹、RocksDB 內(nèi)部都有布隆過(guò)濾器結(jié)構(gòu),布隆過(guò)濾器可以顯著降低數(shù)據(jù)庫(kù)的 IO請(qǐng)求數(shù)量叨咖。當(dāng)用戶(hù)來(lái)查詢(xún)某個(gè) row 時(shí)瘩例,可以先通過(guò)內(nèi)存中的布隆過(guò)濾器過(guò)濾掉大量不存在的row 請(qǐng)求,然后再去磁盤(pán)進(jìn)行查詢(xún)甸各。

郵箱系統(tǒng)的垃圾郵件過(guò)濾功能也普遍用到了布隆過(guò)濾器垛贤,因?yàn)橛昧诉@個(gè)過(guò)濾器,所以平時(shí)也會(huì)遇到某些正常的郵件被放進(jìn)了垃圾郵件目錄中趣倾,這個(gè)就是誤判所致聘惦,概率很低。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末儒恋,一起剝皮案震驚了整個(gè)濱河市善绎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碧浊,老刑警劉巖涂邀,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異箱锐,居然都是意外死亡比勉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)驹止,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浩聋,“玉大人,你說(shuō)我怎么就攤上這事臊恋∫陆啵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵抖仅,是天一觀的道長(zhǎng)坊夫。 經(jīng)常有香客問(wèn)我砖第,道長(zhǎng),這世上最難降的妖魔是什么环凿? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任梧兼,我火速辦了婚禮,結(jié)果婚禮上智听,老公的妹妹穿的比我還像新娘羽杰。我一直安慰自己,他們只是感情好到推,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布考赛。 她就那樣靜靜地躺著,像睡著了一般莉测。 火紅的嫁衣襯著肌膚如雪颜骤。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天捣卤,我揣著相機(jī)與錄音复哆,去河邊找鬼。 笑死腌零,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的唆阿。 我是一名探鬼主播益涧,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驯鳖!你這毒婦竟也來(lái)了闲询?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浅辙,失蹤者是張志新(化名)和其女友劉穎扭弧,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體记舆,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸽捻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泽腮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片御蒲。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诊赊,靈堂內(nèi)的尸體忽然破棺而出厚满,到底是詐尸還是另有隱情,我是刑警寧澤碧磅,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布碘箍,位于F島的核電站遵馆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丰榴。R本人自食惡果不足惜货邓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望多艇。 院中可真熱鬧逻恐,春花似錦、人聲如沸峻黍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姆涩。三九已至挽拂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骨饿,已是汗流浹背亏栈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宏赘,地道東北人绒北。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像察署,于是被迫代替她去往敵國(guó)和親闷游。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在日常生活中贴汪,包括在設(shè)計(jì)計(jì)算機(jī)軟件時(shí)脐往,我們經(jīng)常要判斷一個(gè)元素是否在一個(gè)集合中。比如在字處理軟件中扳埂,需要檢查一個(gè)英語(yǔ)...
    jackcooper閱讀 837評(píng)論 0 4
  • 寫(xiě)在前面 在大數(shù)據(jù)與云計(jì)算發(fā)展的時(shí)代业簿,我們經(jīng)常會(huì)碰到這樣的問(wèn)題。我們是否能高效的判斷一個(gè)用戶(hù)是否訪問(wèn)過(guò)某網(wǎng)站的主頁(yè)...
    Audience0閱讀 1,755評(píng)論 0 0
  • 引言 我們知道檢查一個(gè)元素是否在某一個(gè)集合中阳懂,使用HashSet是比較好的選擇梅尤,因?yàn)樵诓话l(fā)生Hash碰撞的情況下它...
    不知名的程序員閱讀 7,487評(píng)論 0 8
  • 背景 在上篇【鏈接[http://www.jetchen.cn/algorithm-bitmap-bitset/]...
    goldenJetty閱讀 544評(píng)論 0 3
  • 系列文章Redis應(yīng)用-分布式鎖Redis應(yīng)用-異步消息隊(duì)列與延時(shí)隊(duì)列Redis應(yīng)用-位圖Redis應(yīng)用-Hype...
    Gundy_閱讀 524評(píng)論 0 2