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引入:
com.redislabs
jrebloom
1.0.1
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 存在所致。如果这个位数组比较稀疏,判断正确的概率就会很大,如果这个位数组比较拥挤,判断正确的概率就会降低。
7.空间计算
布隆计算器