redis之二十一 -- 布隆过滤器安装与使用及原理分析

本文介绍了如何在Redis中启用布隆过滤器,通过编译或Docker方式,并展示了如何使用bf.add、bf.exists等命令。布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否可能在一个集合中。虽然可能会有误判,但不会漏判。文中还给出了Python实现布隆过滤器的代码示例,以及其在垃圾邮件过滤、URL去重等场景的应用。
摘要由CSDN通过智能技术生成

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

如果使用传统的方式,例如 SQL 中的传统查询,因为数据量太多,查询效率又低有占用系统的资源,因此我们需要一个优秀的算法和功能来实现这个需求,这是我们今天要讲的——布隆过滤器。

开启布隆过滤器

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

方式一:编译方式

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

git clone https://github.com/RedisLabsModules/redisbloom.git

cd redisbloom

make # 编译redisbloom

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

2.修改redis配置文件,使之加载该布隆过滤器,将上述编译后的redisbloom.so添加到配置文件

loadmodule /usr/local/RedisBloom-2.2.9/redisbloom.so

3.启动redis

redis-server   redis.conf 

方式二:Docker 方式

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

启动验证

服务启动之后,我们需要判断布隆过滤器是否正常开启,此时我们只需使用 redis-cli 连接到服务端,输入 bf.add 看有没有命令提示,就可以判断是否正常启动了,如下图所示:

image.png

如果有命令提示则表名 Redis 服务器已经开启了布隆过滤器。(如果没有提示,可以使用命令测试一下,我的没有提示,但是也可以正常使用)

布隆过滤器的使用

布隆过滤器的命令不是很多,主要包含以下几个:

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

具体使用如下所示:

127.0.0.1:6379> bf.add user xiaoming
(integer) 1
127.0.0.1:6379> bf.add user xiaohong
(integer) 1
127.0.0.1:6379> bf.add user laowang
(integer) 1
127.0.0.1:6379> bf.exists user laowang
(integer) 1
127.0.0.1:6379> bf.exists user lao
(integer) 0
127.0.0.1:6379> bf.madd user huahua feifei
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.mexists user feifei laomiao
1) (integer) 1
2) (integer) 0

可以看出以上结果没有任何误差,我们再来看一下准确率 bf.reserve 的使用: 

127.0.0.1:6379> bf.reserve user 0.01 200
(error) ERR item exists #已经存的 key 设置会报错
127.0.0.1:6379> bf.reserve userlist 0.9 10
OK

可以看出此命令必须在元素刚开始执行,否则会报错,它有三个参数:key、error_rate 和 initial_size。

其中:

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

后面原理部分会讲到 error_rate 和 initial_size 对准确率影响的具体原因。

代码实战

下面我们用 python 客户端来实现布隆过滤器的操作,因为 Jedis 没有直接操作布隆过滤器的方法,所以我们使用 python 脚本的方式来实现布隆过滤器,代码如下:

 

from typing import List


class BitVector:
    # cap 代表向量的长度
    def __init__(self, cap: int):
        self.__cap = cap
        self.__size = int((cap + 31 - 1) / 31)
        self.__list = [0] * self.__size

    # 设置pos位为1,就需要先判断该位在哪个int中(idx),
    # 然后再判断在该int的哪一位(sub_idx)
    # 置为1的操作就是先将1移动到sub_idx位,然后再进行 "|" 操作
    def set_bit(self, pos: int):
        idx = int(pos / 31)
        sub_idx = int(pos - idx * 31)
        self.__list[idx] |= 1 << sub_idx

    # 将这一位取出就是需要先找到该位,然后再和 1 进行与操作即可
    # 如果该位为0,则返回值为0,如果该位为0,则返回值不为0(为2的sub_idx次方)
    def get_bit(self, pos: int):
        idx = int(pos / 31)
        sub_idx = int(pos - idx * 31)
        return self.__list[idx] & (1 << sub_idx)


class HashFunc:
    def __init__(self, cap, seed=401):
        self.__cap = cap
        self.__seed = seed

    def get_value(self, value: str):
        hash_value = 0
        for i in range(value.__len__()):
            hash_value = (hash_value * self.__seed + ord(value[i])) % self.__cap

        return hash_value


class BloomFilter:
    # hash_list参数接收多个hash函数的seed,cap代表容量
    def __init__(self, hash_list: List[int], cap):
        self.__hash_list = [HashFunc(cap, seed) for seed in hash_list]
        self.__bit_vector = BitVector(cap)

    # 判断一个值是否存在,如果经过hash映射在位向量的值都不为0,就认为已存在
    # 只要有一个为0,说明一定不存在
    def exists(self, value: str) -> bool:
        for hash_func in self.__hash_list:
            if not self.__bit_vector.get_bit(hash_func.get_value(value)):
                return False

        return True

    # 添加一个数
    # 算出所有hash函数对应的值,以该值作为位向量的下标,将这些位都置为1
    def add(self, value: str):
        for hash_func in self.__hash_list:
            self.__bit_vector.set_bit(hash_func.get_value(value))


if __name__ == '__main__':
    capacity = int(1e4 * 3)
    bloom_filter = BloomFilter([401, 101], capacity)

    add_num = int(1e4)
    test_num = int(1e4 * 2)

    for add_value in range(add_num):
        bloom_filter.add('value' + str(add_value))

    positive_error_count = 0
    negative_error_count = 0
    positive = 0
    negative = 0

    for test_value in range(test_num):
        if bloom_filter.exists('value' + str(test_value)):
            positive += 1
            if test_value >= add_num:
                positive_error_count += 1
        else:
            negative += 1
            if test_value < add_num:
                negative_error_count += 1

    print("positive count: " + str(positive))
    print("negative count: " + str(negative))
    print("positive error count: " + str(positive_error_count))
    print("negative error count: " + str(negative_error_count))
    print("positive error rate: %.3f " % (positive_error_count * 100 / positive) + "%")
    print("negative error rate: %.3f " % (negative_error_count * 100 / negative) + '%')

测试结果:程序认为10252个数据已存在,9748个不存在,实际上是10,000个存在,10,000不存在。判断存在的错误率仅为 2.5%,程序判断不存在就一定不存在,错误率为0。

positive count: 10252
negative count: 9748
positive error count: 252
negative error count: 0
positive error rate: 2.458 %
negative error rate: 0.000 %

原理

上面我们学会了布隆过滤器的使用,下面我们就来看下它的实现原理。

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

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

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

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

image.png

布隆过滤器使用场景

它的经典使用场景包括以下几个:

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

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

小结

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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值