redis高级数据结构---布隆过滤器

从上一节学习了使用HyperLogLog数据结构来进行估数统计,他非常的有价值,在数据量庞大的情况下,低内存消耗的解决很多对精度要求不高的统计问题。

但是加入我们想知道某一个值是不是已经在HyperLogLog结构里面了,此时HyperLogLog显得有些无能为力,因为HyperLogLog只提供了pfadd,pfcount以及pfmerge方法,并没有提供类似于pfcontain方法。

描述一个业务场景,比如我们在使用新闻客户端看新闻的时候,他会不断的给我们推荐新的内容,可以想象一下,在每次推荐的时候,都要去重,去重那写我们已经阅读过的新闻,文章等。那么问题来了,新闻客户端怎么实现推送去重的??

此时我们可以使用正常思维想到,每次推荐的时候,去遍历一遍历史记录,过滤掉哪些已经存在的激励,就可以实现推荐去重了,当用户量很大,每个用户看过的新闻又很多时,这种解决方案还能够奏效么?

历史记录我们可以存储在关系型数据库里或者缓存里,数据库—>难以扛住如此高得并发;缓存—>可能需要跟多的内存来存储历史记录。而且这些存储都是需要永久保存的,随着时间增长,历史记录的数量是随时间呈线性增长的。总有垮台的一天。

此时,redis的高级数据结构就能很好的解决去重的问题,他在起到去重作用的同时,还能节省90%的存储空间,只是稍微有点那么不精确,换句话说,就是存在一定的误判概率。

理解布隆过滤器

布隆过滤器可以理解为一个不怎么精确的set结构,当使用contains方法判断一个对象是否存在时,他可能会误判,与HyperLogLog一样,也不是特别不精确,只要参数配置的合理,精确度也会相对的提高,只会有小小的误判概率。
当布隆过滤器说某个值存在时,这个值可能不存在。
当布隆过滤器说某个值不存在时,这个值一定不存在。

举个例子,当他说不认识你的时候,他一定不认识你,当他说认识你的时候,可能根本没见过你,因为可能跟认识的某个人比较像,所以误判认识你而已。

redis中的布隆过滤器

redis在4.0版本及之后以插件的形式正式登场。接下来安装插件
bloom的官方网址:https://github.com/RedisBloom/RedisBloom
接下来编译

tar zxvf RedisBloom-x.x.x.tar.gz
cd RedisBloom-x.x.x
make

得到动态库rebloom.so
启动redis时,如下启动即可加载bloom filter插件

./redis-server /Users/fengyue/redis-5.0.7/redis.conf --loadmodule /Users/fengyue/Downloads/RedisBloom-1.0.3/rebloom.so INITIAL_SIZE 100 ERROR_RATE 0.01

这边INITIAL_SIZE参数以及ERROR_RATE参数后面会提及。
尝试添加一个布隆过滤器并塞值

fengyuedeMacBook-Pro:src fengyue$ ./redis-cli 
127.0.0.1:6379> bf.add code u1
(integer) 1
127.0.0.1:6379> 

布隆过滤器为我们提供了bf.add和bf.exists方法。bf.add添加元素,bf.exists判断元素是否存在。用法类似于set的sadd,sismember,bf.add只能添加一个参数,如果需要批量添加参数,需要使用bf.madd指令,相对应的如果需要一次查询多个元素是否存在,就需要用到bf.mexists指令。

127.0.0.1:6379> bf.add role user1
(integer) 1
127.0.0.1:6379> bf.add role user
(integer) 1
127.0.0.1:6379> bf.add role user2
(integer) 1
127.0.0.1:6379> bf.add role user3
(integer) 1
127.0.0.1:6379> bf.add role user4
(integer) 1
127.0.0.1:6379> bf.exists role user
(integer) 1
127.0.0.1:6379> bf.exists role user1
(integer) 1
127.0.0.1:6379> bf.madd role user5 user6 user7 
1) (integer) 1
2) (integer) 1
3) (integer) 1
127.0.0.1:6379> bf.mexists role user6 user7 user8
1) (integer) 1
2) (integer) 1
3) (integer) 0
127.0.0.1:6379> 

上面的结果似乎很准确,并没有出现误判的情况。接下来用python脚本加入很多元素,再试试:

import redis

client = redis.StrictRedis()
# 先删除之前的数据
client.delete("role")
for i in range(100000):
    client.execute_command("bf.add", "role", "user%d" % i)
    ret = client.execute_command("bf.exists", "role", "user%d" % i)
    if ret == 0:
        print(i)
        break

执行了很多遍之后,依然没有输出,可见,布隆过滤器对自己见过的元素肯定不会出现误判。现在我们让布隆过滤器去识辨自己没有见过的元素。

import redis

client = redis.StrictRedis()
# 先删除之前的数据
client.delete("role")
for i in range(100000):
    client.execute_command("bf.add", "role", "user%d" % i)
    # 此时布隆过滤器并没有见过i+11
    ret = client.execute_command("bf.exists", "role", "user%d" % (i+11))
    if ret == 1:
        print(i)
        break

当ret==1时,代表bloom判断出存在该元素,可是从代码里面发现,exists的值永远比塞入的值大11,bloom却认识他,说明出现了误判。

上面使用的布隆过滤器只是默认参数的布隆过滤器。他在我们第一次add的时候自动创建。redis其实还提供了自定义参数的布隆过滤器,需要我们在add之前使用bf.reserve指令显式创建。如果对应的key已经存在,bf.reserve会报错。bf.reserve有三个参数,分别是key,error_rate(错误率)和initial_size.

error_rate越低,需要的空间越大。
initial_size表示预计放入的元素数量,当实际数量超过这个数值时,误判率会上升,所以需要提前设置一个较大的数值避免超过导致误判率升高。
如果不使用bf.reserve,默认的error_rate是0.01,默认的initial_size是100。

注意事项:

布隆过滤器的initial_size设置的过大,会浪费存储空间,设置的过小,就会影响准确率,在使用之前一定要尽可能的精确估计元素数量,还要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。

布隆过滤器的error_rate越小,需要的存储空间就越大,对于不需要过于精确的场合,error_rate设置的稍大一点也可以。在某些业务场景中出现误判的情况对用户无感知的时候,误判率也可以忽略不计了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值