我們在使用新聞客戶端看新聞時,它會給我們不停地推薦新的內容,它每次推薦時要去重,去掉那些已經看過的內容。問題來了,新聞客戶端推薦系統如何實現推送去重的?
會想到服務器記錄了用戶看過的所有歷史記錄,當推薦系統推薦新聞時會從每個用戶的歷史記錄里進行篩選,過濾掉那些已經存在的記錄。問題是當用戶量很大,每個用戶看過的新聞又很多的情況下,這種方式,推薦系統的去重工作在性能上跟的上么?

實際上,如果歷史記錄存儲在關系數據庫里,去重就需要頻繁地對數據庫進行 exists 查詢,當系統並發量很高時,數據庫是很難扛住壓力的。
可能又想到了緩存,但是如此多的歷史記錄全部緩存起來,那得浪費多大存儲空間啊?而且這個存儲空間是隨着時間線性增長,你撐得住一個月,你能撐得住幾年么?但是不緩存的話,性能又跟不上,這該怎么辦?
這時,布隆過濾器 (Bloom Filter) 閃亮登場了,它就是專門用來解決這種去重問題的。它在起到去重的同時,在空間上還能節省 90% 以上,只是稍微有那么點不精確,也就是有一定的誤判概率。
布隆過濾器是什么?
布隆過濾器可以理解為一個不怎么精確的 set 結構,當你使用它的 contains 方法判斷某個對象是否存在時,它可能會誤判。但是布隆過濾器也不是特別不精確,只要參數設置的合理,它的精確度可以控制的相對足夠精確,只會有小小的誤判概率。
當布隆過濾器說某個值存在時,這個值可能不存在;當它說不存在時,那就肯定不存在。打個比方,當它說不認識你時,肯定就不認識;當它說見過你時,可能根本就沒見過面,不過因為你的臉跟它認識的人中某臉比較相似 (某些熟臉的系數組合),所以誤判以前見過你。
套在上面的使用場景中,布隆過濾器能准確過濾掉那些已經看過的內容,那些沒有看過的新內容,它也會過濾掉極小一部分 (誤判),但是絕大多數新內容它都能准確識別。這樣就可以完全保證推薦給用戶的內容都是無重復的。
Redis 中的布隆過濾器
Redis 官方提供的布隆過濾器到了 Redis 4.0 提供了插件功能之后才正式登場。布隆過濾器作為一個插件加載到 Redis Server 中,給 Redis 提供了強大的布隆去重功能。
布隆過濾器基本使用
布隆過濾器有二個基本指令,bf.add 添加元素,bf.exists 查詢元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一個元素,如果想要一次添加多個,就需要用到 bf.madd 指令。同樣如果需要一次查詢多個元素是否存在,就需要用到 bf.mexists 指令。
Redis 其實還提供了自定義參數的布隆過濾器,需要我們在 add 之前使用bf.reserve指令顯式創建。如果對應的 key 已經存在,bf.reserve會報錯。bf.reserve有三個參數,分別是 key, error_rate和initial_size。錯誤率越低,需要的空間越大。initial_size參數表示預計放入的元素數量,當實際數量超出這個數值時,誤判率會上升。