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

news2024/9/27 7:16:17

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 方法的 capacity 和 errorRate 参数并未直接用于 BF.ADD 命令,因为 BF.ADD 只是用于向已存在的布隆过滤器中添加元素。如果你需要设置布隆过滤器的容量和错误率,你应该在创建布隆过滤器时使用 BF.RESERVE 命令。

  • 布隆过滤器不支持传统意义上的“删除”操作,因为一旦一个位被设置为 1,它就不能再被设置为 0(除非重新创建布隆过滤器)。

  • 在实际部署之前,请确保你的 Redis 服务器已经安装了 RedisBloom 模块,并且 go-redis/redis 库与你的 Redis 服务器版本兼容。

5 总结

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

文章转载自:Hello-Brand

原文链接:https://www.cnblogs.com/wzh2010/p/18030915

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch导出导入数据

1.概念回顾 2.基础操作 展示详细信息 GET:http://127.0.0.1:9200/_cat/indices?v Java代码将文件导入到ES package com.Graph.medicalgraph;import org.apache.http.HttpHost; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.act…

Java中的位图和布隆过滤器(如果想知道Java中有关位图和布隆过滤器的知识点,那么只看这一篇就足够了!)

前言:在学习之前的数据结构的时候,我们使用的数据量都不是很大,但是在生活中,我们常常面临着要处理大量数据结果并得出最终结果,那么有没有什么数据结构可以帮助我们实现这样的功能呢? ✨✨✨这里是秋刀鱼不…

Circular dependency between the following tasks(gradle循环依赖的问题)

简介 目前公司项目主要使用gradle而不是maven,所以对gradle的使用不是很清楚,遇到这个问题的时候一直在晚上查资料,但是解决方案基本都是安卓的,后续先前辈请教了一下,才找到解决方案。 问题解析 本质上就是两个模块…

【QT 5 调试软件+Linux下调用脚本shell-无法调度+目录拼写+无法找目录+sudo权限(2)+问题解决方式+后续补充】

【QT 5 调试软件Linux下调用脚本shell-无法调度目录拼写无法找目录sudo权限(2)问题解决方式后续补充】 1、前言2、问题综述:自研qt上位机无法调度脚本(1)可能原因1:无法找到目录情况说明:解决思…

程序人生:软件测试 非技术性面试题【建议每个测试人观看】

1、自我介绍:三分钟左右 2、为什么从郑州/太原离职? 3、你的职业规划是什么样的? 4、对下一家公司有什么自己的想法吗? 5、你觉得作为一名测试工程师,应该具备什么样的素养? 6、你觉得管理层&#xff…

HTML | 外部引入 CSS 的2种方式:link和@import有什么区别?

外部引入 CSS 有2种方式,link 和 import。就结论而言,强烈建议使用 link ,慎用 import 方式。 两者都是外部引用 CSS 的方式,但是存在一定的区别: (1)从属关系区别 link是HTML / XHTML标签&a…

react crash course 2024(3) jsx语法及组件

创建组件的文件结构 rafce创建组件内容 const NavBar () > {return (<div>NavBar</div>) }export default NavBar 使用该组件 组件传值 或者把props解构 设置默认值 5.用自定义组件包裹一些元素 把他们传值 元素直接变成了参数被传进来

求推荐网站建设公司?2024网站建设公司哪家好TOP3

好的网站建设公司在这个信息时代寻找起来会比较困难&#xff0c;因为你在任何一个搜索引擎搜索“网站建设”这个关键词&#xff0c;平台会给你推送成千上万家的建站公司&#xff0c;让人看得眼花缭乱&#xff0c;但究竟哪个更好&#xff0c;让人捉摸不透。 前段时间&#xff0…

多城联动、多形式开展网安周公益活动,开源网安传播网络安全知识

9月9日至15日&#xff0c;以“网络安全为人民&#xff0c;网络安全靠人民”为主题的2024年国家网络安全宣传周将在全国范围内统一开展&#xff0c;通过多样的形式、丰富的内容&#xff0c;助力全社会网络安全意识和防护技能提升。开源网安今年继续为各地企业、群众带来了丰富的…

Qt QFileDialog使用方法

头文件 #include <QFileDialog> 成员名称返回值说明getExistingDirectoryQString返回用户选中的文件夹路径getExistingDirectoryUrlQUrl与QFileDialog::getExistingDirectory()的主要区别来自于为用户提供的选择远程目录的能力getOpenFileNameQString返回用户选中的文件…

从更底层的角度理解网站的访问过程

文章目录 1.示例&#xff0c;访问www.baidu.com是如何返回数据的1.输入www.baidu.com回车2.检查本机的C:\Windows\System32\drivers\etc\hosts配置文件夹下有没有这个域名对应的映射&#xff1a; 1.示例&#xff0c;访问www.baidu.com是如何返回数据的 1.输入www.baidu.com回车…

18.2 k8s-apiserver监控源码解读

本节重点介绍 : k8s代码库和模块地址 下载 apiserver源码 apiserver中监控源码阅读 k8s源码地址分布 k8s代码库 访问github上k8s仓库&#xff0c;readme中给出了k8s 模块的代码地址举例图片 组件仓库列表 地址 Repositories currently staged here: k8s.io/apik8s.io/a…

Maven配置、下载教程(非常详细)

maven下载地址 Maven – 发行说明 – Maven 3.6.1 (apache.org) 1.配置settings.xml 下载完maven之后&#xff0c;保存在电脑中并解压 打开maven文件 -->conf-->settings.xml 使用记事本方式打开 打开之后找到这个地方&#xff0c;在电脑中创建一个文件夹(repository)…

起重机防摇摆技术如何达标-武汉正向科技

武汉正向科技防摇摆控制器 主要技术参数 1、防摇摆精度&#xff1a; 0.4 2、行车到达目标位置偏差位置偏差&#xff1a; 25mm 3、通讯方式&#xff1a;PROFINET / PROFIBUS / RS232 / RS422 / RS485&#xff1b; 4、消除载荷的摇摆达 96% 以上&#xff1b; 5、技术先进…

图像面积计算一般方法及MATLAB实现

一、引言 在数字图像处理中&#xff0c;经常需要获取感兴趣区域的面积属性&#xff0c;下面给出图像处理的一般步骤。 1.读入的彩色图像 2.将彩色图像转化为灰度图像 3.灰度图像转化为二值图像 4.区域标记 5.对每个区域的面积进行计算和显示 二、程序代码 %面积计算 cle…

- 串口通信

USART串口通信 目录 USART串口通信 回顾 USART串口通信 1、通信分类与作用 2、串口通信的相关参数&#xff08;重点&#xff09; 3、位协议层 -- RS232协议 4、STM32F103 中的串口外设 5、调试串口编程 -- &#xff08;1&#xff09;串口初始化&#xff1a;时钟、IO、…

【Git入门】使用 Git 进行项目管理:Word Count 程序开发与托管

在软件开发过程中&#xff0c;版本控制工具是不可或缺的。Git 作为一款强大的分布式版本控制工具&#xff0c;为开发者提供了高效的代码管理和协作方式。本博客将介绍如何下载安装 Git 版本管理工具&#xff0c;并使用 Git 和 GitHub 平台进行一个名为 Word Count 的项目开发与…

【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS

【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS 一、环境说明二、关系型数据库RDS介绍三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本&#xff1a; API版本&#xff1a;以12为主 二、关系型数据库RDS介绍 1. RDS关系型数据库简介&#xff1a; 关系型数…

VMware提供虚拟硬盘并使得Oracle Linux集群共享块设备并绑定raw设备。

一、Vmware操作 nodeA : 1、创建SCSI控制器:类型为物理。 添加新磁盘。 类型为独立-持久。 nodeB: 新增磁盘,但是选择node A刚才创建的磁盘。 类型:独立-持久。 二、OS层操作 两台都要做绑定。 详细步骤 1. 创建 raw 设备节点 首先,确保 /dev/raw 目录存在。如果不…

5分钟精通Excel在go中的使用

一些简单操作可以在官方文档中找到&#xff0c;应该足够无经验的朋友们入门 介绍 - 《Excelize v2.2 中文文档》 - 书栈网 BookStack 这里贴一个中文版的链接&#xff08;以excelize库为例&#xff0c;相对其他库来说&#xff0c;体验很不错&#xff09;&#xff0c;不过要注…