• 15. 使用布隆过滤器从海量数据中查询一个值是否存在


    楔子

    我们前面介绍过 HyperLogLog 可以用来做基数统计,但它没提供判断一个值是否存在的查询方法,那我们如何才能在海量数据之中判断一个值是否存在呢?

    因为是海量数据,所以我们就无法将每个键值都存起来,然后再从结果中检索数据了,比如数据库中的 select count(1) from tablename where id='XXX',或者是使用 Redis 普通的查询方法 get XXX 等方式,这样是不合适的。我们只能依靠专门处理此问题的特殊方法来实现数据的查找。

    而完成一个功能的就是我们今天的主角:布隆过滤器。

    布隆过滤器

    什么是布隆过滤器

    布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。也就是说布隆过滤器的优点就是计算和查询速度很快,但是缺点也很明显,就是存在一定的误差。

    但是在 Redis 中不能直接使用布隆过滤器,但我们可以通过 Redis 4.0 版本之后提供的 modules(扩展模块)的方式引入,本文提供两种方式的开启方式。

    方式一:编译方式

    1. 下载并安装布隆过滤器

    git clone https://github.com/RedisLabsModules/redisbloom.git
    cd redisbloom
    make # 编译redisbloom
    

    编译正常执行完,会在根目录生成一个 redisbloom.so 文件。

    2. 启动 Redis 服务器

    ./redis-server redis.conf --loadmodule redisbloom.so文件路径
    

    其中 --loadmodule 为加载扩展模块的意思,后面跟的是 redisbloom.so 文件的路径。

    方式二:docker方式

    也是本文所使用的方式,直接拉取镜像然后运行即可。

    docker pull redislabs/rebloom  # 拉取镜像
    docker run -p 6379:6379 redislabs/rebloom  # 运行容器
    

    布隆过滤器的使用

    关于布隆过滤器的原理我们后面会说,目前的话先来看看使用。布隆过滤器的命令不是很多,主要包含以下几个:

    • bf.add:添加元素
    • bf.exists:判断某个元素是否存在
    • bf.madd:添加多个元素
    • bf.mexists:判断多个元素是否存在
    • bf.reserve:设置布隆过滤器的准确率

    举栗说明:

    添加元素:

    127.0.0.1:6379> bf.add user mea
    (integer) 1
    127.0.0.1:6379> bf.add user kano
    (integer) 1
    127.0.0.1:6379> bf.add user nana
    (integer) 1
    127.0.0.1:6379> 
    

    判断元素是否存在:

    127.0.0.1:6379> bf.exists user mea
    (integer) 1
    127.0.0.1:6379> bf.exists user mea1
    (integer) 0
    127.0.0.1:6379>
    

    添加多个元素:

    127.0.0.1:6379> bf.madd vtb mea kano nana
    1) (integer) 1
    2) (integer) 1
    3) (integer) 1
    127.0.0.1:6379>
    

    判断多个元素是否存在:

    127.0.0.1:6379> bf.mexists user mea kano kano1
    1) (integer) 1
    2) (integer) 1
    3) (integer) 0
    127.0.0.1:6379> 
    

    可以看出以上结果没有任何误差,然后还有一个布隆过滤器的准确率,不过在介绍它之前,我们先使用Python操作一下Redis的布隆过滤器。

    我们之前使用Python操作Redis使用第三方模块也叫redis,但是对于布隆过滤器来说,这个模块是不支持的,我们需要使用另一个第三方模块:redisbloom,直接pip install redisbloom安装即可。

    其实redisbloom底层还是使用了我们之前的redis模块。

    from redisbloom.client import Client
    
    # 我们之前创建连接使用的是redis.Redis,而这个Client继承自Redis
    # 所以我们之前的一些操作,这里都可以用
    client = Client(host="47.94.174.89", decode_responses="utf-8")
    
    # 添加单个元素
    client.bfAdd("VTuber", "mea")
    
    # 添加多个元素
    client.bfMAdd("VTuber", "kano", "nana")
    
    # 判断单个元素是否存在
    print(client.bfExists("VTuber", "kano"))  # 1
    
    # 判断多个元素是否存在
    print(client.bfMExists("VTuber", "kano", "nana", "mea", "xxx"))  # [1, 1, 1, 0]
    

    设置布隆过滤器的准确率:

    127.0.0.1:6379> bf.reserve user 0.01 200
    (error) ERR item exists  # 对于一个已经存在的key,不可以使用bf.reserve
    127.0.0.1:6379> bf.reserve user1 0.01 200  
    OK
    127.0.0.1:6379>
    

    key后面的两个值分别表示:error_rate、initial_size

    • error_rate:允许布隆过滤器的错误率,这个值越低过滤器占用空间也就越大,以为此值决定了位数组的大小,位数组是用来存储结果的,它的空间占用的越大 (存储的信息越多),错误率就越低,它的默认值是 0.01;
    • initial_size:布隆过滤器存储的元素大小,实际存储的值大于此值,准确率就会降低,它的默认值是 100。

    布隆过滤器常见使用场景有:

    • 垃圾邮件过滤;
    • 爬虫里的 URL 去重;
    • 判断一个值在亿级数据中是否存在。

    布隆过滤器在数据库领域的使用也比较广泛,例如:HBase、Cassandra、LevelDB、RocksDB 内部都有使用布隆过滤器。

    对于布隆过滤器而言,数据量增大到一定程度之后误差也会随之增大。

    原理解析

    Redis 布隆过滤器的实现,依靠的是它数据结构中的一个位数组,每次存储键值的时候,不是直接把数据存储在数据结构中,因为这样太占空间了,它是利用几个不同的无偏哈希函数,把此元素的 hash 值均匀的存储在位数组中,也就是说,每次添加时会通过几个无偏哈希函数算出它的一些位置,把这些位置设置成 1 就完成了添加操作。

    当进行元素判断时,查询此元素的几个哈希位置上的值是否为 1,如果全部为 1,则表示此值存在,如果有一个值为 0,则表示不存在。因为此位置是通过 hash 计算得来的,所以即使这个位置是 1,并不能确定是一定是该元素把它标识为 1 的,因此布隆过滤器查询此值存在时,此值不一定存在,但查询此值不存在时,此值一定不存在。

    并且当位数组存储值比较稀疏的时候,查询的准确率越高,而当位数组存储的值越来越多时,误差也会增大。

    位数组和 key 之间的关系,如下图所示:

    小结

    通过本文我们知道可以使用 Redis 4.0 之后提供的 modules 方式来开启布隆过滤器,并学习了布隆过滤器的三个重要操作方法 bf.add 添加元素、bf.exists 查询元素是否存在,还有 bf.reserve 设置布隆过滤器的准确率,其中 bf.reserve 有 2 个重要的参数:错误率和数组大小,错误率设置的越低,数组设置的越大,需要存储的空间就越大,相对来说查询的错误率也越低,需要如何设置需要使用者根据实际情况进行调整。我们也知道布隆过滤器的特点:当它查询有数据时,此数据不一定真的存在,当它查询没有此数据时,此数据一定不存在。

  • 相关阅读:
    文档视图
    引入缓冲池技术
    数据库访问与查询
    OnInitialUpdate函数
    显示股票视图的全局函数
    切换视图的核心代码
    GuiEdit的使用
    操作方法
    SQL 使用 解析
    调用API 实现 窗体 拖动
  • 原文地址:https://www.cnblogs.com/traditional/p/13337690.html
Copyright © 2020-2023  润新知