首页 > 数据库 >Redis系列补充:聊聊布隆过滤器(go语言实践篇)

Redis系列补充:聊聊布隆过滤器(go语言实践篇)

时间:2024-09-24 08:54:42浏览次数:7  
标签:BF 缓存 err redis Redis 布隆 过滤器 go

Redis24篇集合

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 的步骤如下:
image

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 表示不存在。可以一次性判断单个元素,或者一次性判断多个元素。

image

综上,我们通过几个指令就能实现布隆过滤器的建设,避免缓存穿透的情况发生。
如果你要查询缓存信息,必须先到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 方法的 capacityerrorRate 参数并未直接用于 BF.ADD 命令,因为 BF.ADD 只是用于向已存在的布隆过滤器中添加元素。如果你需要设置布隆过滤器的容量和错误率,你应该在创建布隆过滤器时使用 BF.RESERVE 命令。
  • 布隆过滤器不支持传统意义上的“删除”操作,因为一旦一个位被设置为 1,它就不能再被设置为 0(除非重新创建布隆过滤器)。
  • 在实际部署之前,请确保你的 Redis 服务器已经安装了 RedisBloom 模块,并且 go-redis/redis 库与你的 Redis 服务器版本兼容。

5 总结

本篇介绍了布隆过滤器的几种实现场景。
并以火车票订单信息查询为案例进行说明,如何使用布隆过滤器避免缓存穿透,避免被恶意攻击。

标签:BF,缓存,err,redis,Redis,布隆,过滤器,go
From: https://www.cnblogs.com/wzh2010/p/18030915

相关文章

  • GOTS认证是什么意思?GOTS认证对象、审核范围及GOTS认证机构
    GOTS认证,即全球有机纺织品标准认证,是一种国际性的有机纺织品认证体系,旨在确保纺织和服装产品从原材料到最终产品的整个生产链都符合有机生产标准,满足消费者的健康、环保和公平贸易的需求。GOTS认证的对象主要是纺织和服装产品的生产链,包括有机纤维的生产、加工、制造和贸易等......
  • Python-django-flask毕业设计项目选择管理系统 1j23s
    目录技术栈和环境说明python语言解决的思路具体实现截图框架介绍技术路线操作可行性性能/安全/负载方面python-flask核心代码部分展示python-django核心代码部分展示详细视频演示源码获取技术栈和环境说明本系统的开发与设计是基于vue为前端页面核心框架为django/fl......
  • 76.最小覆盖子串 Golang实现
    题目描述:给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串""。注意:对于t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。如果s中存在这样的子串,我们保证它是唯一的答......
  • 【开题报告】基于django+vue旅游景点预约系统(论文+源码) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着旅游业的蓬勃发展,人们对旅游体验的需求日益多样化与个性化。传统的旅游预约方式往往存在信息不对称、预约流程繁琐、效率低下等问题,已......
  • 万恶的goto关键字
    提到goto,大家一定能想到迪杰斯特拉发表的著名论文goto有害论(GoToStatementConsideredHarmful)。正是它推动了结构化程序设计语言的发展。公正地说,goto并非那么可怕,机器码/汇编码本身支持跳转,就是goto的底层形态。计算机程序中条件选择、循环等语句最终依然依靠跳转指......
  • Go 学习路线图
    基础阶段学习内容:掌握Go的基本语法,包括变量、常量、数据类型(如整数、浮点数、字符串、布尔值、数组、切片、映射等)、运算符等。理解程序的控制流,如条件语句(if-else、switch-case)、循环语句(for、while等)。学会使用函数来封装代码,理解函数的参数、返回值、函数的定义和调用。......
  • 【解决方案】Java 互联网项目中常见的 Redis 缓存应用场景
    一、常见key-value首先介绍的是项目开发中常见的一些String类型的key-value结构场景,如:使用jsonStr结构存储的用户登录信息,包括:手机号、token、唯一uuid、昵称等;jsonStr结构某个热门商品的信息,包括:商品名称、商品唯一id、所属商家、价格等;String类型的、......
  • 10分钟速成golang
    Go拥有命令式语言的静态类型,编译很快,执行也很快,同时加入了对于目前多核CPU的并发计算支持,也有相应的特性来实现大规模编程。//单行注释/*多行注释*///导入包的子句在每个源文件的开头。//main比较特殊,它用来声明可执行文件,而不是一个库。packagemain//Import......
  • 优化 Go 语言数据打包:性能基准测试与分析
    优化Go语言数据打包:性能基准测试与分析场景:在局域网内,需要将多个机器网卡上抓到的数据包同步到一个机器上。原有方案:tcpdump-w写入文件,然后定时调用rsync进行同步。改造方案:使用Go重写这个抓包逻辑及同步逻辑,直接将抓到的包通过网络发送至服务端,由服务端写入,这样就减少......
  • Go 语言编程极简教程 2
    Go语言编程极简教程2我将为您提供一个Go语言编程的极简教程。我会尽量详细地解释每个步骤,并探讨多种方法来介绍这个主题。让我们开始吧!文章目录Go语言编程极简教程2介绍Go语言安装Go语言环境创建第一个Go程序解释Go程序结构Go语言的基本数据类型变量声......