Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战

硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战

作者头像
码哥字节
发布于 2022-04-08 08:45:22
发布于 2022-04-08 08:45:22
15.1K00
代码可运行
举报
文章被收录于专栏:Java 技术栈Java 技术栈
运行总次数:0
代码可运行

Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?中我们说到可以使用布隆过滤器避免「缓存穿透」。

码哥,布隆过滤器还能在哪些场景使用呀?

比如我们使用「码哥跳动」开发的「明日头条」APP 看新闻,如何做到每次推荐给该用户的内容不会重复,过滤已经看过的内容呢?

你会说我们只要记录了每个用户看过的历史记录,每次推荐的时候去查询数据库过滤存在的数据实现去重

实际上,如果历史记录存储在关系数据库里,去重就需要频繁地对数据库进行 exists 查询,当系统并发量很高时,数据库是很难扛住压力的。

码哥,我可以使用缓存啊,把历史数据存在 Redis 中。

万万不可,这么多的历史记录那要浪费多大的内存空间,所以这个时候我们就能使用布隆过滤器去解决这种去重问题。又快又省内存,互联网开发必备杀招!

当你遇到数据量大,又需要去重的时候就可以考虑布隆过滤器,如下场景:

  • 解决 Redis 缓存穿透问题(面试重点);
  • 邮件过滤,使用布隆过滤器实现邮件黑名单过滤;
  • 爬虫爬过的网站过滤,爬过的网站不再爬取;
  • 推荐过的新闻不再推荐;

什么是布隆过滤器

布隆过滤器 (Bloom Filter)是由 Burton Howard Bloom 于 1970 年提出,它是一种 space efficient 的概率型数据结构,用于判断一个元素是否在集合中

当布隆过滤器说,某个数据存在时,这个数据可能不存在;当布隆过滤器说,某个数据不存在时,那么这个数据一定不存在。

哈希表也能用于判断元素是否在集合中,但是布隆过滤器只需要哈希表的 1/8 或 1/4 的空间复杂度就能完成同样的问题。

布隆过滤器可以插入元素,但不可以删除已有元素。

其中的元素越多,false positive rate(误报率)越大,但是 false negative (漏报)是不可能的。

布隆过滤器原理

BloomFilter 的算法是,首先分配一块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0。

加入元素时,采用 k 个相互独立的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1。

检测 key 是否存在,仍然用这 k 个 Hash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。

如下图所示:

布隆过滤器原理

哈希函数会出现碰撞,所以布隆过滤器会存在误判。

这里的误判率是指,BloomFilter 判断某个 key 存在,但它实际不存在的概率,因为它存的是 key 的 Hash 值,而非 key 的值。

所以有概率存在这样的 key,它们内容不同,但多次 Hash 后的 Hash 值都相同。

对于 BloomFilter 判断不存在的 key ,则是 100% 不存在的,反证法,如果这个 key 存在,那它每次 Hash 后对应的 Hash 值位置肯定是 1,而不会是 0。布隆过滤器判断存在不一定真的存在。

码哥,为什么不允许删除元素呢?

删除意味着需要将对应的 k 个 bits 位置设置为 0,其中有可能是其他元素对应的位。

因此 remove 会引入 false negative,这是绝对不被允许的。

Redis 集成布隆过滤器

Redis 4.0 的时候官方提供了插件机制,布隆过滤器正式登场。以下网站可以下载官方提供的已经编译好的可拓展模块。

https://redis.com/redis-enterprise-software/download-center/modules/

RedisModules

码哥推荐使用 Redis 版本 6.x,最低 4.x 来集成布隆过滤器。如下指令查看版本,码哥安装的版本是 6.2.6。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
redis-server -v
Redis server v=6.2.6 sha=00000000:0 malloc=libc bits=64 build=b5524b65e12bbef5

下载

我们自己编译安装,需要从 github 下载,目前的 release 版本是 v2.2.14,下载地址:https://github.com/RedisBloom/RedisBloom/releases/tag/v2.2.14

Redis 布隆

解压编译

解压

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
tar -zxvf RedisBloom-2.2.14.tar

编译插件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cd RedisBloom-2.2.14
make

编译成功,会看到 redisbloom.so 文件。

安装集成

需要修改 redis.conf 文件,新增 loadmodule配置,并重启 Redis。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
loadmodule /opt/app/RedisBloom-2.2.14/redisbloom.so

如果是集群,则每个实例的配置文件都需要加入配置。

module 配置

指定配置文件并启动 Redis:

redis-server /opt/app/redis-6.2.6/redis.conf

加载成功的页面如下:

加载布隆过滤器成功

客户端连接 Redis 测试。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BF.ADD --添加一个元素到布隆过滤器
BF.EXISTS --判断元素是否在布隆过滤器
BF.MADD --添加多个元素到布隆过滤器
BF.MEXISTS --判断多个元素是否在布隆过滤器

测试

Redis 布隆过滤器实战

我们来用布隆过滤器来解决缓存穿透问题,缓存穿透:意味着有特殊请求在查询一个不存在的数据,即数据不存在 Redis 也不存在于数据库。

当用户购买商品创建订单的时候,我们往 mq 发送消息,把订单 ID 添加到布隆过滤器。

订单同步到布隆过滤器

在添加到布隆过滤器之前,我们通过BF.RESERVE命令手动创建一个名字为 orders error_rate = 0.1 ,初始容量为 10000000 的布隆过滤器:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]
BF.RESERVE orders 0.1 10000000
  • key:filter 的名字;
  • error_rate:期望的错误率,默认 0.1,值越低,需要的空间越大;
  • capacity:初始容量,默认 100,当实际元素的数量超过这个初始化容量时,误判率上升。
  • EXPANSION:可选参数,当添加到布隆过滤器中的数据达到初始容量后,布隆过滤器会自动创建一个子过滤器,子过滤器的大小是上一个过滤器大小乘以 expansion;expansion 的默认值是 2,也就是说布隆过滤器扩容默认是 2 倍扩容;
  • NONSCALING:可选参数,设置此项后,当添加到布隆过滤器中的数据达到初始容量后,不会扩容过滤器,并且会抛出异常((error) ERR non scaling filter is full) 说明:BloomFilter 的扩容是通过增加 BloomFilter 的层数来完成的。每增加一层,在查询的时候就可能会遍历多层 BloomFilter 来完成,每一层的容量都是上一层的两倍(默认)。

如果不使用BF.RESERVE命令创建,而是使用 Redis 自动创建的布隆过滤器,默认的 error_rate0.01capacity是 100。

布隆过滤器的 error_rate 越小,需要的存储空间就越大,对于不需要过于精确的场景,error_rate 设置稍大一点也可以。

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

添加订单 ID 到过滤器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# BF.ADD {key} {item}
BF.ADD orders 10086
(integer) 1

使用 BF.ADD向名称为 orders 的布隆过滤器添加 10086 这个元素。

如果是多个元素同时添加,则使用 BF.MADD key {item ...},如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BF.MADD orders 10087 10089
1) (integer) 1
2) (integer) 1

判断订单是否存在

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# BF.EXISTS {key} {item}
BF.EXISTS orders 10086
(integer) 1

BF.EXISTS 判断一个元素是否存在于BloomFilter,返回值 = 1 表示存在。

如果需要批量检查多个元素是否存在于布隆过滤器则使用 BF.MEXISTS,返回值是一个数组:

  • 1:存在;
  • 0:不存在。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# BF.MEXISTS {key} {item}
BF.MEXISTS orders 100 10089
1) (integer) 0
2) (integer) 1

总体说,我们通过BF.RESERVE、BF.ADD、BF.EXISTS三个指令就能实现避免缓存穿透问题。

码哥,如何查看创建的布隆过滤器信息呢?

BF.INFO key查看,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BF.INFO orders
 1) Capacity
 2) (integer) 10000000
 3) Size
 4) (integer) 7794184
 5) Number of filters
 6) (integer) 1
 7) Number of items inserted
 8) (integer) 3
 9) Expansion rate
10) (integer) 2

返回值:

  • Capacity:预设容量;
  • Size:实际占用情况,但如何计算待进一步确认;
  • Number of filters:过滤器层数;
  • Number of items inserted:已经实际插入的元素数量;
  • Expansion rate:子过滤器扩容系数(默认 2);

码哥,如何删除布隆过滤器呢?

目前布隆过滤器不支持删除,布谷过滤器Cuckoo Filter是支持删除的。

Bloom 过滤器在插入项目时通常表现出更好的性能和可伸缩性(因此,如果您经常向数据集添加项目,那么 Bloom 过滤器可能是理想的)。布谷鸟过滤器在检查操作上更快,也允许删除。

大家有兴趣可可以看下:https://oss.redis.com/redisbloom/Cuckoo_Commands/)

码哥,我想知道你是如何掌握这么多技术呢?

其实我也是翻阅官方文档并做一些简单加工而已,这篇的文章内容实战就是基于 Redis 官方文档上面的例子:https://oss.redis.com/redisbloom/。

大家遇到问题一定要耐心的从官方文档寻找答案,培养自己的阅读和定位问题的能力。

Redission 布隆过滤器实战

码哥的样例代码基于 Spring Boot 2.1.4,代码地址:https://github.com/MageByte-Zero/springboot-parent-pom。

添加 Redission 依赖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<dependency>
  <groupId>org.redisson</groupId>
  <artifactId>redisson-spring-boot-starter</artifactId>
  <version>3.16.7</version>
</dependency>

使用 Spring boot 默认的 Redis 配置方式配置 Redission:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
spring:
  application:
    name: redission

  redis:
    host: 127.0.0.1
    port: 6379
    ssl: false

创建布隆过滤器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class BloomFilterService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 创建布隆过滤器
     * @param filterName 过滤器名称
     * @param expectedInsertions 预测插入数量
     * @param falseProbability 误判率
     * @param <T>
     * @return
     */
    public <T> RBloomFilter<T> create(String filterName, long expectedInsertions, double falseProbability) {
        RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(filterName);
        bloomFilter.tryInit(expectedInsertions, falseProbability);
        return bloomFilter;
    }

}

单元测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedissionApplication.class)
public class BloomFilterTest {

    @Autowired
    private BloomFilterService bloomFilterService;

    @Test
    public void testBloomFilter() {
        // 预期插入数量
        long expectedInsertions = 10000L;
        // 错误比率
        double falseProbability = 0.01;
        RBloomFilter<Long> bloomFilter = bloomFilterService.create("ipBlackList", expectedInsertions, falseProbability);

        // 布隆过滤器增加元素
        for (long i = 0; i < expectedInsertions; i++) {
            bloomFilter.add(i);
        }
        long elementCount = bloomFilter.count();
        log.info("elementCount = {}.", elementCount);

        // 统计误判次数
        int count = 0;
        for (long i = expectedInsertions; i < expectedInsertions * 2; i++) {
            if (bloomFilter.contains(i)) {
                count++;
            }
        }
        log.info("误判次数 = {}.", count);
        bloomFilter.delete();
    }
}

注意事项:如果是 Redis Cluster 集群,则需要 RClusteredBloomFilter<SomeObject> bloomFilter = redisson.getClusteredBloomFilter("sample")

参考资料

1.https://blog.csdn.net/u010066934/article/details/122026625

2.https://juejin.cn/book/6844733724618129422/section/6844733724706209806

3.https://www.cnblogs.com/heihaozi/p/12174478.html

4.https://www.cnblogs.com/allensun/archive/2011/02/16/1956532.html

5.https://oss.redis.com/redisbloom/Bloom_Commands/

6.https://oss.redis.com/redisbloom/

7.https://redis.com/blog/rebloom-bloom-filter-datatype-redis

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码哥字节 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
高频面试考点:解读布隆过滤器
布隆过滤器,起源于 20 世纪 70 年代,是一种高效的数据过滤算法。它基于二进制数组和多哈希函数的结合,以极低的空间占用和高效率著称。由于其底层采用位存储方式,使得存储成本大幅降低,例如,仅需 10KB 的空间便能存储高达百万个元素的数组。
写bug的高哈哈
2025/01/05
810
高频面试考点:解读布隆过滤器
布隆过滤器
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Vincent-yuan
2022/05/06
4530
布隆过滤器
不了解布隆过滤器?一文给你整的明明白白!
海量数据处理以及缓存穿透这两个场景让我认识了布隆过滤器 ,我查阅了一些资料来了解它,但是很多现成资料并不满足我的需求,所以就决定自己总结一篇关于布隆过滤器的文章。希望通过这篇文章让更多人了解布隆过滤器,并且会实际去使用它!
乔戈里
2019/12/11
9750
不了解布隆过滤器?一文给你整的明明白白!
Redis布隆过滤器原理与实践
在高并发请求时,业务数据一般会对数据进行缓存,提高系统并发量,因为磁盘IO和网络IO相对于内存IO的成百上千倍的性能劣势。 做个简单计算,如果我们需要某个数据,该数据从数据库磁盘读出来需要0.1s,从交换机传过来需要0.05s,那么每个请求完成最少0.15s(当然,事实上磁盘和网络IO也没有这么慢,这里只是举例),该数据库服务器每秒只能响应67个请求;而如果该数据存在于本机内存里,读出来只需要10us,那么每秒钟能够响应100,000个请求。 通过将高频使用的数据存在离cpu更近的位置,以减少数据传输时间,从而提高处理效率,这就是缓存的意义。
全栈程序员站长
2022/11/08
4760
Redis布隆过滤器原理与实践
布隆过滤器 | 亿级数据处理原理与实战
布隆过滤器(英语:Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。
Rude3Knife的公众号
2020/05/15
2.1K0
详细解析Redis中的布隆过滤器及其应用
布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它可以告诉你某种东西一定不存在或者可能存在。当布隆过滤器说,某种东西存在时,这种东西可能不存在;当布隆过滤器说,某种东西不存在时,那么这种东西一定不存在。
Bug开发工程师
2020/02/19
2.3K0
详细解析Redis中的布隆过滤器及其应用
布隆过滤器能解决哪些问题?
举个例子 : 有 50 亿个电话号码,现在给你 10 万个电话号码,如何快速准确的判断出这些号码是否存在?
用户11397231
2024/12/10
970
布隆过滤器能解决哪些问题?
布隆过滤器原理和使用场景
Bloom Filter 会使用一个较大的 bit 数组来保存所有的数据,数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1(代表 false 或者 true),用于检索元素是否存在于大集合中的数据结构。
卷福同学
2025/02/19
1620
一文搞懂布隆过滤器
在开发软件时,我们经常需要判断一个元素是否在一个集合中,比如,如何判断单词的拼写是否错误(判断单词是否在已知的字典中);在网络爬虫里,如何确认一个网址是否已经爬取过;反垃圾邮件系统中,如何判断一个邮件地址是否为垃圾邮件地址等等。
somenzz
2021/09/14
3470
Guava -- Bloom Filter原理
去重在软件开发中经常需要用到,在Java当中一般使用Set集合,面对大量数据则可以利用取MD5签名等值后再进行去重,然而Set集合的实现原理决定了如果有大量的key需要判断,必然会需要大量的内存来支撑,且随着数据量增大效率也变得不那么尽人意。另外业务中存在着很多对精确性不需要那么高的场景,此时使用Set集合则是一种资源浪费,因此就可以利用布隆过滤器等算法手段进行去重。
屈定
2020/02/10
1.7K0
好玩的布隆过滤器
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 「“某样东西一定不存在或者可能存在”」。
chengcheng222e
2021/11/04
3750
redis缓存穿透穿透解决方案-布隆过滤器
相信绝大多数同学都是这么处理请求的,这样用redis能够给mysql抵挡住大部分的请求。其实这样是存在一定的问题的
程序员小饭
2020/10/26
6550
redis缓存穿透穿透解决方案-布隆过滤器
Reids(4)——神奇的HyperLoglog解决统计问题
上一次 我们学会了使用 HyperLogLog 来对大数据进行一个估算,它非常有价值,可以解决很多精确度不高的统计需求。但是如果我们想知道某一个值是不是已经在 HyperLogLog 结构里面了,它就无能为力了,它只提供了 pfadd 和 pfcount 方法,没有提供类似于 contains 的这种方法。
我没有三颗心脏
2020/03/20
7480
Reids(4)——神奇的HyperLoglog解决统计问题
基于Redis扩展模块的布隆过滤器使用
什么是布隆过滤器? 它实际上是一个很长的二进制向量和一系列随机映射函数。把一个目标元素通过多个hash函数的计算,将多个随机计算出的结果映射到二进制向量的位中,依次来间接标记一个元素是否存在于一个集合中。 布隆过滤器可以做什么? 布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。 布隆过滤器特点 如果布隆过滤器显示一个元素不存在于集合中,那么这个元素100%不存在与集合当中 如果布隆过滤器显示一个元素存在于集合中,那么很有可能存在,可能性取决于对布隆过滤器的定义(BF.RESERVE {key} {error_rate} {capacity})
星哥玩云
2022/08/18
5850
基于Redis扩展模块的布隆过滤器使用
Redis布隆过滤器
布隆过滤器是一种概率型数据结构(Probabilistic data structures),对插入和查询比较高效,能够计算 “某样东西 一定不存在 或者 可能存在 ”。
玖柒的小窝
2021/12/09
4220
Redis布隆过滤器
Redis布隆过滤器实现与总结
问题一:原本有10亿个号码,现在又来了10万个号码,要快速准确判断这10万个号码是否在10亿个号码库中?
兔云小新LM
2021/04/13
3.3K0
Redis布隆过滤器实现与总结
redis中的布隆过滤器
redis 在 4.0 的版本中加入了 module 功能,布隆过滤器可以通过 module 的形式添加到 redis 中,所以使用 redis 4.0 以上的版本可以通过加载 module来使用 redis 中的布隆过滤器。但是这不是最简单的方式,使用 docker 可以直接在 redis 中体验布隆过滤器。
名字是乱打的
2021/12/22
6310
基于 Redis 布隆过滤器实现海量数据去重及其在 PHP 爬虫系统中的应用
在上篇教程中,学院君给大家介绍了 UV 统计功能的实现思路,如果访问量较小,使用 SET 即可,如果访问量很大,可以使用 HyperLogLog 来降低存储空间和优化性能。
学院君
2021/01/22
2K0
Redis布隆Bloom过滤器
  Redis提供了三种强大数据结构:HyperLogLog,布隆过滤器和布谷鸟过滤器。本文讨论布隆过滤器:
物流IT圈
2019/07/16
1.5K0
布隆过滤器
布隆过滤器(Bloom Filter)是1970年由一个叫布隆的人提出的,它本质是一个很长的二进制向量(位数组)和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。其优点是空间效率和查询时间都比一般的算法好太多,这是布隆过滤器的出名之处。缺点是有一定的误识别率和删除困难
晚上没宵夜
2022/05/09
3910
布隆过滤器
相关推荐
高频面试考点:解读布隆过滤器
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验