1.使用場景:推薦系統給用戶推薦新聞,避免重復推送。
需要考慮問題:從用戶觀看歷史中篩選出沒有看過的新聞進行推送,就需要數據庫中頻繁的使用exists進行查詢,但是當用戶量很大時,數據庫很難頂住壓力。
解決方法:
1.1.使用緩存?但是日子長了,會浪費很大空間,不是長久之計,不是很好的解決辦法。
1.2.這時布隆過濾器就可以很好的解決這個需求了,可以節約90%以上的空間,缺點就是稍微有那么一點不准確,存在一定的誤判率,但是對於這個新聞推送的可以忽略。
2.什么布隆過濾器
2.1其實布隆過濾器可以看成是一個不是很准確的set結構,只是在使用它的contains方法判斷某個對象是否存在時會出現誤判。但是它也不是特別的不精准,只要參數設置合理,那么它的精確度可以控制的足夠精准,只會有小小的誤判。
2.2當布隆過濾器說某個值存在時,那可能就不存在,如果說某個值不存在時,那肯定就是不存在了。
打個比方,當一個人說認識你時可能不認識你,當一個人說不認識你時那肯定就不認識了。當它說見過你時,可能根本沒有見過面,只不過可能你的臉和它所認識人中某個人的臉相似度比較高,所以產生誤判。
2.3對於上面的場景,當用戶看過的新聞,肯定會被過濾掉,對於沒有看多的新聞,可能會過濾極少的一部分(誤判),但是絕大部分都可以准確識別。這樣可以完全保證推送給用戶的新聞都是無重復的。
3.centos安裝redis的bloomfilter插件
https://blog.csdn.net/u013030276/article/details/88350641
4.bloomfilter使用
4.1 bf.add
語法:[bf.add key options]
127.0.0.1:6379> bf.add users user3
(integer) 1
4.2 bf.exists
語法:[bf.exists key options]
127.0.0.1:6379> bf.exists users user1
(integer) 1
127.0.0.1:6379> bf.exists users user3
(integer) 0
4.3 bf.madd
語法:[bf.add key ...options]
127.0.0.1:6379> bf.madd users user4 user5 user6 user7
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
4.4 bf.mexists
語法:[bf.add key ...options]
127.0.0.1:6379> bf.mexists users user4 user5 user6 user7 user8
1) (integer) 1
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 0
4.5 bf.reserve創建Filter
語法:[bf.reserve key error_rate initial_size]
127.0.0.1:6379> bf.reserve books 0.001 10000
OK
5.代碼實現
5.1引入依賴
jedis3.0沒有rebloom的相關方法,只能通過引入rebloom.jar。
https://github.com/RedisLabs/JReBloom
pom.xml引入:
<dependencies> <dependency> <groupId>com.redislabs</groupId> <artifactId>jrebloom</artifactId> <version>1.0.1</version> </dependency> </dependencies>
5.2對自己見過的元素判斷是否不存在
@Test
public void test3() {
setJedisPool();
Client client = new Client(jedisPool);
jedisPool.getResource().del("book");
for (int i = 0; i < 10000; i++) {
String book = "book" + i;
client.add("book", book);
boolean ret = client.exists("book", book); // 判斷自己見過的,沒有出現誤判
if (!ret) {
System.out.println(i);
}
}
jedisPool.close();
}
5.3使用默認Filter參數(),對自己沒有見過的,判斷是否存在
@Test
public void test4() {
setJedisPool();
Client client = new Client(jedisPool);
int count = 0;
jedisPool.getResource().del("book");
for (int i = 0; i < 10000; i++) {
String book = "book" + i;
boolean ret = client.exists("book", book); // 判斷自己沒見過的,誤判數153個,15.3%
if (ret) {
count++;
System.out.println(i + "誤判數:" + count);
}
client.add("book", book);
}
jedisPool.close();
}
10000個元素,判斷自己沒見過的,誤判數153個,1.53%
對於超過1.5%的誤判率怎么辦呢?
默認的Filter的initial_size=100,error_rate=0.1;
5.4創建Filter(initial_size,error_rate),對自己沒有見過的,判斷是否存在
@Test
public void test5() {
setJedisPool();
Client client = new Client(jedisPool);
int count = 0;
jedisPool.getResource().del("book");
client.createFilter("book", 9000, 0.001);
for (int i = 0; i < 10000; i++) {
String book = "book" + i;
boolean ret = client.exists("book", book); // 判斷自己沒見過的,10000,0.001誤判數0個;9000,0.001誤判1個;
if (ret) {
count++;
}
System.out.println(book + "--" + ret + "--誤判數:" + count);
client.add("book", book);
}
jedisPool.close();
}
設置預計放入的元素個數=10000,錯誤率=0.001誤判數0個;
設置預計放入的元素個數=9000,錯誤率=0.001誤判1個;
注意事項:
布隆過濾器的initial_size估計的過大,所需要的空間就越大,會浪費空間,估計的過小會影響准確率,
因此在使用前一定要估算好元素數量,還需要加上一定的冗余空間以避免實際元素高出預估數量造成誤差過大。
布隆過濾器的error_rate越小,所需要的空間就會越大,對於不需要過於准確的,error_rate設置的稍大一點也無所謂。
6.布隆過濾器原理
每個布隆過濾器對應到 Redis 的數據結構里面就是一個大型的位數組和幾個不一樣的無偏 hash 函數。所謂無偏就是能夠把元素的 hash 值算得比較均勻。
向布隆過濾器中添加 key 時,會使用多個 hash 函數對 key 進行 hash 算得一個整數索引值然后對位數組長度進行取模運算得到一個位置,每個 hash 函數都會算得一個不同的位置。再把位數組的這幾個位置都置為 1 就完成了 add 操作。
向布隆過濾器詢問 key 是否存在時,跟 add 一樣,也會把 hash 的幾個位置都算出來,看看位數組中這幾個位置是否都為 1,只要有一個位為 0,那么說明布隆過濾器中這個 key 不存在。
如果都是 1,這並不能說明這個 key 就一定存在,只是極有可能存在,因為這些位被置為 1 可能是因為其它的 key 存在所致。如果這個位數組比較稀疏,判斷正確的概率就會很大,如果這個位數組比較擁擠,判斷正確的概率就會降低。
原文鏈接:https://blog.csdn.net/u013030276/article/details/88381868
