Redis系列补充:聊聊布隆过滤器(go语言实践篇)
1 介绍
布隆过滤器(Bloom Filter)是 Redis 4.0 版本之后提供的新功能,我们一般将它当做插件加载到 Redis Service服务器中,给 Redis 提供强大的滤重功能。
它是一种概率性数据结构,可用于判断一个元素是否存在于一个集合中。相比较之 Set 集合的去重功能,布隆过滤器空间上能节省 90% +,不足之处是去重率大约在 99% 左右,那就是有 1% 左右的误判率,这种误差是由布隆过滤器的自身结构决定的。它有如下优缺点:
- 优点:空间效率和查询时间都比一般的算法要好的多
- 缺点:有一定的误识别率和删除困难
详细的原理可以参考笔者的这一篇《聊聊布隆过滤器(原理篇)》。
2 应用场景说明
我们在遇到数据量大的时候,为了去重并避免大批量的重复计算,可以考虑使用 Bloom Filter 进行过滤。
具体常用的经典场景如下:
- 解决大流量下缓存穿透的问题,参考笔者这篇《一次缓存雪崩的灾难复盘》。
- 过滤被屏蔽、拉黑、减少推荐的信息,一般你在浏览抖音或者百度App的时候,看到不喜欢的会设置减少推荐、屏蔽此类信息等,都可以采用这种原理设计。
- 各种名单过滤,使用布隆过滤器实现第一层的白名单或者黑名单过滤,可用于各种AB场景。
下面以缓存穿透为解决目标进行案例介绍。
3 案例分析
布隆过滤器的一个经典应用场景就是解决缓存穿透问题!
缓存穿透是指访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量井喷时会导致DB挂掉。
比如 我们查询用户的信息,程序会根据用户的编号去缓存中检索,如果找不到,再到数据库中搜索。如果你给了一个不存在的编号:XXXXXXXX,那么每次都比对不到,就透过缓存进入数据库。
这样风险很大,如果因为某些原因导致大量不存在的编号被查询,甚至被恶意伪造编号进行大规模攻击,那将是灾难。
解决方案质疑就是在缓存之前在加一层 BloomFilter :
- 把存在的key记录在BloomFilter中,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在则说明数据库和缓存都没有,就直接返回,
- 存在再走查缓存 ,投入数据库去查询,这样减轻了数据库的压力。
3.1 巨量查询场景
下面以火车票订购和查询为案例进行说明,如果火车票被恶意攻击,模拟了一样结构的火车票订单编号,那很可能通过大量的请求穿透过缓存层把数据库打雪崩了,所以使用布隆过滤器为服务提供一层保障。
具体的做法就是,我们在购买火车票成功的时候,把订单号的ID写入(异步或者消息队列的方式)到布隆过滤器中,保障后续的查询都在布隆过滤器中走一遍再进到缓存中去查询。
3.2 创建Bloom Filter
创建 Bloom Filter 的语法如下:
# BF.RESERVE {key} {error_rate} {capacity} [EXPANSION {expansion}] [NONSCALING]
BF.RESERVE ticket_orders 0.01 1000000
这边的命令是通过BF.RESERVE命令手动创建一个名字为 ticket_orders,错误率为 0.01 ,初始容量为 1000000 的布隆过滤器。
这边需要注意的一些点是:
- error_rate 越小,对碰撞的容忍度越小,需要的存储空间就越大。如果允许一定比例的不准确,对精确度要求不高的场景,error_rate 可以设的稍大一点。
- capacity 设置的过大,会浪费存储空间,设置过小,准确度不高。所以评估的时候需要精准一点,既要避免浪费空间也要保证准确比例。
原理不理解的请参考笔者的这一篇《聊聊布隆过滤器(原理篇)》。
3.3 创建车票订单
# BF.ADD {key} {value ... }
# 添加单个订单号
BF.ADD ticket_orders 1725681193-350000
(integer) 1
# 添加多个订单号
BF.MADD ticket_orders 1725681193-350000 1725681197-270001 1725681350-510007
1) (integer) 1
2) (integer) 1
3) (integer) 1
以上的语句是将已经订好的车票订单号存储到Bloom Filter中,包括一次存储单个和一次存储多个。
火车票订单同步到 Bloom Filter 的步骤如下:

3.4 判断火车票订单Id是否存在
# BF.EXISTS {key} {value} ,存在的话返回 1,不存在返回 0
BF.EXISTS ticket_orders 1725681193-350000
(integer) 1
# 批量判断多个值是否存在于布隆过滤器,语句如下:
BF.MEXISTS ticket_orders 1725681193-350000 1725681197-270001 1725681350-510007
1) (integer) 0
2) (integer) 1
3) (integer) 0
BF.EXISTS 判断一个元素是否存在于 Bloom Filter中,返回值 = 1 表示存在,返回值 = 0 表示不存在。可以一次性判断单个元素,或者一次性判断多个元素。

综上,我们通过几个指令就能实现布隆过滤器的建设,避免缓存穿透的情况发生。
如果你要查询缓存信息,必须先到Bloom Filter中先跑一次,不存在的直接过滤掉,这样就不会因为无效的key把缓存打穿。
4 程序实现说明
可以在 Golang 中使用 go-redis/redis 库来封装布隆过滤器功能。
你需要先确保你的 Redis 服务器已经安装了 RedisBloom 模块,因为 Redis 本身并不直接支持布隆过滤器。一旦 RedisBloom 安装并配置好,你就可以在 Go 代码中通过 go-redis/redis 库来调用相关的 RedisBloom 命令。
package bloomfilter
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
// BloomFilter 封装了与布隆过滤器相关的操作
type BloomFilter struct {
rdb *redis.Client
name string
}
// NewBloomFilter 创建一个新的布隆过滤器实例
func NewBloomFilter(rdb *redis.Client, name string) *BloomFilter {
return &BloomFilter{
rdb: rdb,
name: name,
}
}
// Add 将元素添加到布隆过滤器中
func (bf *BloomFilter) Add(ctx context.Context, item string, capacity int64, errorRate float64) error {
// 注意:RedisBloom 的 BF.ADD 命令通常不需要显式设置容量和错误率,
// 因为这些是在创建布隆过滤器时设置的。这里我们简化为只添加元素。
// 如果需要动态调整这些参数,你可能需要重新创建布隆过滤器。
// 但为了示例,我们假设这些参数在创建布隆过滤器时已经设置好了。
_, err := bf.rdb.Do(ctx, "BF.ADD", bf.name, item).Result()
return err
}
// Exists 检查元素是否可能存在于布隆过滤器中
func (bf *BloomFilter) Exists(ctx context.Context, item string) (bool, error) {
result, err := bf.rdb.Do(ctx, "BF.EXISTS", bf.name, item).Int()
if err != nil {
return false, err
}
// BF.EXISTS 返回 1 表示可能存在,0 表示一定不存在
return result == 1, nil
}
// 注意:在实际应用中,你可能还需要封装更多操作,比如删除布隆过滤器(虽然布隆过滤器通常不支持删除单个元素)
// 或者调整布隆过滤器的容量和错误率(这通常意味着需要重新创建布隆过滤器)。
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis 地址
Password: "", // 密码(如果有的话)
DB: 0, // 使用的数据库
})
bf := NewBloomFilter(rdb, "myBloomFilter")
ctx := context.Background()
// 添加元素
err := bf.Add(ctx, "item1", 100000, 0.01) // 注意:BF.ADD 命令通常不需要 capacity 和 errorRate
if err != nil {
panic(err)
}
// 检查元素是否存在
exists, err := bf.Exists(ctx, "item1")
if err != nil {
panic(err)
}
fmt.Println("Exists:", exists)
exists, err = bf.Exists(ctx, "item2")
if err != nil {
panic(err)
}
fmt.Println("Exists:", exists)
}
// 注意:上面的 Add 方法中的 capacity 和 errorRate 参数在 BF.ADD 命令中并不直接使用,
// 因为 RedisBloom 的 BF.ADD 命令主要用于添加元素到已存在的布隆过滤器中。
// 容量和错误率通常在创建布隆过滤器时通过 BF.RESERVE 命令设置。
重要提示:
- 在上面的代码中,
Add方法的capacity和errorRate参数并未直接用于BF.ADD命令,因为BF.ADD只是用于向已存在的布隆过滤器中添加元素。如果你需要设置布隆过滤器的容量和错误率,你应该在创建布隆过滤器时使用BF.RESERVE命令。 - 布隆过滤器不支持传统意义上的“删除”操作,因为一旦一个位被设置为 1,它就不能再被设置为 0(除非重新创建布隆过滤器)。
- 在实际部署之前,请确保你的 Redis 服务器已经安装了 RedisBloom 模块,并且
go-redis/redis库与你的 Redis 服务器版本兼容。
5 总结
本篇介绍了布隆过滤器的几种实现场景。
并以火车票订单信息查询为案例进行说明,如何使用布隆过滤器避免缓存穿透,避免被恶意攻击。
Redis系列补充:聊聊布隆过滤器(go语言实践篇)的更多相关文章
- 浅谈redis的HyperLogLog与布隆过滤器
首先,HyperLogLog与布隆过滤器都是针对大数据统计存储应用场景下的知名算法. HyperLogLog是在大数据的情况下关于数据基数的空间复杂度优化实现,布隆过滤器是在大数据情况下关于检索一个元 ...
- Redis解读(4):Redis中HyperLongLog、布隆过滤器、限流、Geo、及Scan等进阶应用
Redis中的HyperLogLog 一般我们评估一个网站的访问量,有几个主要的参数: pv,Page View,网页的浏览量 uv,User View,访问的用户 一般来说,pv 或者 uv 的统计 ...
- 09 redis中布隆过滤器的使用
我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容.问题来了,新闻客户端推荐系统如何实现推送去重的? 会想到服务器记录了用户看过的所有历史记录,当推 ...
- 详细解析Redis中的布隆过滤器及其应用
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货. 什么是布隆过滤器 布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它可以告 ...
- Redis详解(十三)------ Redis布隆过滤器
本篇博客我们主要介绍如何用Redis实现布隆过滤器,但是在介绍布隆过滤器之前,我们首先介绍一下,为啥要使用布隆过滤器. 1.布隆过滤器使用场景 比如有如下几个需求: ①.原本有10亿个号码,现在又来了 ...
- Redis中的布隆过滤器及其应用
什么是布隆过滤器 布隆过滤器(Bloom Filter)是由Howard Bloom在1970年提出的一种比较巧妙的概率型数据结构,它可以告诉你某种东西一定不存在或者可能存在.当布隆过滤器说,某种东西 ...
- 细谈布隆过滤器及Redis实现
何为布隆过滤器? 本质上是一种数据结构,是1970年由布隆提出的.它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数).可以用于检索一个元素是否在一个集合中. 数据结构: 布隆过 ...
- 第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中
第三百五十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详 ...
- 布隆过滤器(Bloom Filter)简要介绍
一种节省空间的概率数据结构 布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判.但是布隆过滤器也不是特别不精确,只要参数设置的 ...
- 三十七 Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中
Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中,判断URL是否重复 布隆过滤器(Bloom Filter)详解 基本概念 如 ...
随机推荐
- 重磅消息:微软发布多平台应用UI框架 MAUI,网友直呼:牛x
本文内容来自微软开发博客:https://devblogs.microsoft.com/dotnet/introducing-net-multi-platform-app-ui/ 转载请注明来源,公众 ...
- AT_abc246_d 题解
洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定整数 \(N\),请你找到最小的整数 \(X\),满足: \(X \ge N\). ...
- 1分钟了解HashSet的使用
前言:刷leetcode的时候体验到hashset有多厉害了,用了他剪枝之后直接不爆超时了.速度大大滴快 使用方法 1.创建set对象Set<Integer>set=new HashSet ...
- 【Java】爬资源案例
也不知道为什么喜欢叫爬虫 搞明白原理之后原来就是解析网页代码获取关键字符串 现在的网页有很多解析出来就是JS了,根本不暴露资源地址 依赖一个JSOUP,其他靠百度CV实现 <!-- https: ...
- Meta公司的Llama3大语言模型
Github地址: https://github.com/meta-llama/llama3 官方介绍: https://ai.meta.com/blog/meta-llama-3/ 官方项目主页: ...
- Google的蛋白质结构预测项目代码(Demo)
相关: Artificial Intelligence | 60 Minutes Full Episodes
- If Messi doesn't understand how to respect others, then he also doesn't deserve to receive respect from others.
If Messi doesn't understand how to respect others, if he doesn't understand the spirit of honoring c ...
- 新购的HP品牌台式机(暗影精灵,自带windows10系统,显卡为RTX2080,CPU为i7-10700)安装双系统(Ubuntu系统),不识别显卡,不识别硬盘 —— 解决方案
事件起因是实验室的师弟要弄深度学习,实验室为其新购一台台式机(HP台式机,暗影精灵,自带windows10系统,显卡为RTX2080,CPU为i7-10700),师弟是满心喜悦的在windows系统上 ...
- [学习笔记] 树链剖分(重链剖分) - 图论 & 数据结构
树链剖分 树链剖分,用于解决一系列的树中链上问题的算法(数据结构).其实对于树链修改和树链求和问题可以使用更加方便的树上差分解决,但是对于像求树链最大(小)权值之类的更复杂的问题,差分就显得不够用了. ...
- CUDA常见编译器配置问题一览
CUDA常见编译器配置问题一览 关注TechLead,复旦博士,分享云服务领域全维度开发技术.拥有10+年互联网服务架构.AI产品研发经验.团队管理经验,复旦机器人智能实验室成员,国家级大学生赛事评审 ...