当前位置: 首页 > article >正文

【Redis】布隆过滤器应对缓存穿透的go调用实现

布隆过滤器

https://pkg.go.dev/github.com/bits-and-blooms/bloom/v3

  • 作用:
    • 判断一个元素是不是在集合中
  • 工作原理:
    1. 一个位数组bit array),初始全为0
    2. 多个哈希函数,运算输入,从而映射到位数组的不同位索引上,对应值改为1
  • 布隆过滤器是在redis外层的,对redis的请求先走布隆,布隆判断查询的数据是缓存命中的,那么走redis,否则拦截。通过这样来处理缓存穿透问题。

一些值得注意的点

  • 同一输入用hash运算得来的位数组上的多个对应位置是可能相同的,即不同输入,可能得到同一输出。所以布隆过滤器有误判的风险,不过用来处理缓存穿透是合适的。
  • 假如输入是“hello”,经过hash后对应0、1索引上的值变为1,现在又输入“你好”,hash后是1、2索引上的值变为1,如果我要删除hello,就会导致你好也被破坏。所以(基础布隆过滤器)无法删除元素。
  • 输入“hello”和“你好”经过hash后的对应位可能相同,这就是误判的情况,如果实际缓存中只有“hello”那么查询“你好”也会被引导到redis。
  • 假如现在要查“hello”但是0、1上的预期值不为1,那么“hello”一定不在缓存。
  • 总结:布隆过滤器可以判断“可能存在”和“一定不存在

实现细节梳理:

  • 可以弄一个布隆预热函数,运行时先从redis读取所有缓存id运算好对应二进制数组的位置,这样就相当于把当前所有的缓存的”特征值“都存到布隆过滤器了,(也可以开个定期触发的协程,不断调用)
package mainimport ("context""fmt""log""sync""time""github.com/bits-and-blooms/bloom/v3""github.com/go-redis/redis/v8"
)var (bloomFilter *bloom.BloomFiltercache       sync.MapredisClient *redis.Client // Redis客户端filterLock  sync.Mutexctx         = context.Background()
)func init() {// 初始化Redis连接redisClient = redis.NewClient(&redis.Options{Addr:     "localhost:6379", // Redis地址Password: "",               // 密码DB:       0,                // 数据库})// 初始化布隆过滤器bloomFilter = bloom.NewWithEstimates(1_000_000, 0.001)// 从Redis预热布隆过滤器if err := preheatBloomFilter(); err != nil {log.Fatalf("Failed to preheat bloom filter: %v", err)}
}// preheatBloomFilter 从Redis加载存在的key
func preheatBloomFilter() error {start := time.Now()log.Println("Starting bloom filter preheating...")// 1. 使用SCAN迭代所有product key(生产环境建议使用特定前缀)var cursor uint64var keys []stringfor {var err error// 假设product key的格式为 product:1001keys, cursor, err = redisClient.Scan(ctx, cursor, "product:*", 1000).Result()if err != nil {return fmt.Errorf("Redis SCAN failed: %w", err)}// 将找到的key添加到布隆过滤器for _, key := range keys {// 提取纯ID(假设key格式为product:{id})id := key[8:] // 跳过"product:"前缀bloomFilter.AddString(id)}if cursor == 0 { // 迭代结束break}}// 2. 或者如果使用Set存储所有ID(更推荐的方式)// 假设所有产品ID存储在product:ids集合中ids, err := redisClient.SMembers(ctx, "product:ids").Result()if err != nil {return fmt.Errorf("Failed to get product IDs: %w", err)}for _, id := range ids {bloomFilter.AddString(id)}log.Printf("Bloom filter preheated. Total keys: %d, Duration: %v", len(ids)+len(keys), time.Since(start))return nil
}// 定期重建布隆过滤器(可选)
func startBloomFilterRebuildJob() {ticker := time.NewTicker(1 * time.Hour)go func() {for range ticker.C {filterLock.Lock()if err := preheatBloomFilter(); err != nil {log.Printf("Failed to rebuild bloom filter: %v", err)}filterLock.Unlock()}}()
}// getProduct 获取商品信息(带Redis缓存)
func getProduct(ctx context.Context, productID string) (string, error) {// 1. 布隆过滤器检查if !bloomFilter.TestString(productID) {return "", fmt.Errorf("商品不存在")}// 2. 检查Redis缓存cacheKey := "product:" + productIDval, err := redisClient.Get(ctx, cacheKey).Result()if err == nil {return val, nil}// 3. 查询数据库(这里演示直接返回)// 实际应该查询真实数据库,这里返回模拟数据productData := "商品详情数据"// 4. 将新数据写入Redisif err := redisClient.Set(ctx, cacheKey, productData, randomExpiration(30*time.Minute, 5*time.Minute)).Err(); err != nil {log.Printf("Failed to set Redis cache: %v", err)}// 5. 更新布隆过滤器(如果确认是新key)filterLock.Lock()bloomFilter.AddString(productID)filterLock.Unlock()return productData, nil
}// 生成随机过期时间(防雪崩)
func randomExpiration(base, randomRange time.Duration) time.Duration {return base + time.Duration(rand.Int63n(int64(randomRange)))
}

代码

bloom.go

package cacheimport ("context""errors""github.com/bits-and-blooms/bloom/v3""github.com/redis/go-redis/v9"pkgredis "shorturl/pkg/db/redis"
)// BloomFilter 布隆过滤器接口
type BloomFilter interface {// Add 添加元素到布隆过滤器Add(key string, value string) error// Exists 检查元素是否可能存在于布隆过滤器中Exists(key string, value string) (bool, error)
}// RedisBloomFilter 基于Redis的布隆过滤器实现
type RedisBloomFilter struct {redisClient *redis.Clientdestroy     func()// 布隆过滤器参数filter *bloom.BloomFilterkey    string // 布隆过滤器在Redis中的键名
}// NewRedisBloomFilter 创建一个新的Redis布隆过滤器
func NewRedisBloomFilter(client *redis.Client, key string, expectedItems int, errorRate float64, destroy func()) BloomFilter {// 使用bits-and-blooms库创建布隆过滤器filter := bloom.NewWithEstimates(uint(expectedItems), errorRate)return &RedisBloomFilter{redisClient: client,destroy:     destroy,filter:      filter,key:         key,}
}// Add 添加元素到布隆过滤器
func (bf *RedisBloomFilter) Add(key string, value string) error {// 添加到内存中的布隆过滤器bf.filter.AddString(value)// 将布隆过滤器的位数组序列化并存储到Redisbits, err := bf.filter.MarshalBinary()if err != nil {return err}// 存储到Redisreturn bf.redisClient.Set(context.Background(), bf.key, bits, 0).Err()
}// Exists 检查元素是否可能存在于布隆过滤器中
func (bf *RedisBloomFilter) Exists(key string, value string) (bool, error) {// 从Redis获取布隆过滤器的位数组bits, err := bf.redisClient.Get(context.Background(), bf.key).Bytes()if err != nil {if errors.Is(err, redis.Nil) {// 如果布隆过滤器不存在,则元素一定不存在return false, nil}return false, err}// 反序列化布隆过滤器if err := bf.filter.UnmarshalBinary(bits); err != nil {return false, err}// 检查元素是否可能存在return bf.filter.TestString(value), nil
}// BloomFilterFactory 布隆过滤器工厂接口
type BloomFilterFactory interface {// NewBloomFilter 创建一个新的布隆过滤器实例NewBloomFilter(key string, expectedItems int, errorRate float64) BloomFilter
}// RedisBloomFilterFactory 基于Redis的布隆过滤器工厂
type RedisBloomFilterFactory struct {redisPool pkgredis.RedisPool
}// NewRedisBloomFilterFactory 创建一个新的Redis布隆过滤器工厂
func NewRedisBloomFilterFactory(redisPool pkgredis.RedisPool) BloomFilterFactory {return &RedisBloomFilterFactory{redisPool: redisPool,}
}// NewBloomFilter 创建一个新的布隆过滤器实例
func (f *RedisBloomFilterFactory) NewBloomFilter(key string, expectedItems int, errorRate float64) BloomFilter {client := f.redisPool.Get()return NewRedisBloomFilter(client, key, expectedItems, errorRate, func() {f.redisPool.Put(client)})
}

bloom.go梳理和功能总结:

1. 核心功能

该文件实现了一个基于 Redis 的布隆过滤器(Bloom Filter),并提供了工厂模式来创建布隆过滤器实例。


2. 主要接口与结构

(1) BloomFilter 接口

定义了布隆过滤器的基本操作:

  • Add(key string, value string) error:将元素添加到布隆过滤器。
  • Exists(key string, value string) (bool, error):检查元素是否可能存在于布隆过滤器中。
(2) RedisBloomFilter 结构

实现了 BloomFilter 接口,基于 Redis 存储布隆过滤器的位数组:

  • 字段
    • redisClient *redis.Client:Redis 客户端。
    • destroy func():释放 Redis 连接的回调函数。
    • filter *bloom.BloomFilter:内存中的布隆过滤器实例。
    • key string:布隆过滤器在 Redis 中的键名。
  • 方法
    • Add:将元素添加到内存中的布隆过滤器,并将位数组序列化后存储到 Redis。
    • Exists:从 Redis 获取布隆过滤器的位数组,反序列化后检查元素是否存在。
(3) BloomFilterFactory 接口

定义了布隆过滤器工厂的基本操作:

  • NewBloomFilter(key string, expectedItems int, errorRate float64) BloomFilter:创建一个新的布隆过滤器实例。
(4) RedisBloomFilterFactory 结构

实现了 BloomFilterFactory 接口,用于创建基于 Redis 的布隆过滤器实例:

  • 字段
    • redisPool pkgredis.RedisPool:Redis 连接池。
  • 方法
    • NewBloomFilter:从连接池获取 Redis 客户端,创建一个新的布隆过滤器实例,并在销毁时释放 Redis 连接。

3. 关键逻辑

(1) 布隆过滤器的初始化
  • 使用 github.com/bits-and-blooms/bloom/v3 库创建布隆过滤器实例:

    filter := bloom.NewWithEstimates(uint(expectedItems), errorRate)
    
  • 参数说明:

    • expectedItems:预计插入的元素数量。
    • errorRate:允许的误报率。
(2) 元素的添加
  • 将元素添加到内存中的布隆过滤器:

    bf.filter.AddString(value)
    
  • 将布隆过滤器的位数组序列化后存储到 Redis:

    bits, err := bf.filter.MarshalBinary()
    if err != nil {return err
    }
    return bf.redisClient.Set(context.Background(), bf.key, bits, 0).Err()
    
(3) 元素的存在性检查
  • 从 Redis 获取布隆过滤器的位数组:

    bits, err := bf.redisClient.Get(context.Background(), bf.key).Bytes()
    
  • 如果 Redis 中不存在该键,则返回 false 表示元素一定不存在。

  • 反序列化布隆过滤器并检查元素是否存在:

    if err := bf.filter.UnmarshalBinary(bits); err != nil {return false, err
    }
    return bf.filter.TestString(value), nil
    
(4) 工厂模式
  • 工厂模式用于管理 Redis 连接池,确保每个布隆过滤器实例使用独立的 Redis 连接,并在销毁时释放连接:
    client := f.redisPool.Get()
    return NewRedisBloomFilter(client, key, expectedItems, errorRate, func() {f.redisPool.Put(client)
    })
    

4. 依赖库

  • github.com/bits-and-blooms/bloom/v3:布隆过滤器的核心实现。
  • github.com/redis/go-redis/v9:Redis 客户端。
  • shorturl/pkg/db/redis:自定义的 Redis 连接池封装。

https://github.com/0voice

相关文章:

【Redis】布隆过滤器应对缓存穿透的go调用实现

布隆过滤器 https://pkg.go.dev/github.com/bits-and-blooms/bloom/v3 作用: 判断一个元素是不是在集合中 工作原理: 一个位数组(bit array),初始全为0。多个哈希函数,运算输入,从而映射到位数…...

【LLM】解锁Agent协作:深入了解谷歌 A2A 协议与 Python 实现

人工智能(AI)智能体正迅速成为企业提高生产力、自动化工作流程和增强运营能力的关键工具。从处理日常重复性任务到协助复杂的决策,智能体的潜力巨大。然而,当这些智能体来自不同的供应商、使用不同的框架或被限制在孤立的数据系统…...

kafka4.0浅尝辄止

最近工作中接触消息队列比较多,前几周又看到kafka4.0发布,故写一篇博客对消息队列做一个复盘。 目录 消息队列对比1. Apache Kafka 4.02. RabbitMQ3. RocketMQ4. ActiveMQ5. Apache Pulsar6. NSQ kafka4.0鲜明的新特性Java 版本要求升级API 更新与精简移…...

数据库原理及应用mysql版陈业斌实验三

🏝️专栏:Mysql_猫咪-9527的博客-CSDN博客 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 实验三多表查询 1.实验数据如下 student 表(学生表&#…...

OpenHarmony - 小型系统内核(LiteOS-A)(二)

OpenHarmony - 小型系统内核(LiteOS-A)(二) 三、基础内核 3.1、中断及异常处理 基本概念 中断是指出现需要时,CPU暂停执行当前程序,转而执行新程序的过程。即在程序运行过程中,出现了一个必须…...

数字化引擎再升级:小匠物联十周年庆典与全链路创新实践

4月11日,浙江宁波的小匠物联十周年庆典拉开帷幕。本次活动以“拾阶而上,智创未来”为主题,从全员签到、心愿书写,到董事长致辞、切蛋糕及全体合影,每一个环节都精心设计,展现出企业在家用物联网领域的卓越技…...

机器学习核心知识:从基础概念到关键算法

摘要 本文深度剖析机器学习知识体系,从基本概念、学习方式,到分类算法、逻辑回归等关键内容均有涉及。详细阐述各知识点原理与应用场景,并对比多种算法的优劣。 关键词:机器学习;监督学习;分类算法&#x…...

开发工具-jetbrains使用技巧

更详细的可以看 狂神说Java】JavaWeb入门到实战 p6 idea中maven的操作 可以设置怎么调试 然后还可以wsl、远程方式等运行 maven 这里的相当于cmd的操作 命令行去执行这些东西...

HarmonyOS:页面滚动时标题悬浮、背景渐变

一、需求场景 进入到app首页或者分页列表首页时,随着页面滚动,分类tab要求固定悬浮在顶部。进入到app首页、者分页列表首页、商品详情页时,页面滚动时,顶部导航栏(菜单、标题)背景渐变。 二、相关技术知识点…...

Python——Matplotlib库的练习

1、 import matplotlib.pyplot as plt import numpy as npx np.linspace(0,2*np.pi,100) y1 np.sin(x) y2 np.cos(x)plt.plot(x,y1,"r--o",linewidth1.5,markersize6) plt.plot(x,y2,"g-s",linewidth2,markersize8)plt.show() 2、 import matplotlib…...

信息系统项目管理师-第十八章-项目绩效域

本文章记录学习过程中,重要的知识点,是否为重点的依据,来源于官方教材和历年考题,持续更新共勉 本文章记录学习过程中,重要的知识点,是否为重点的依据,来源于官方教材和历年考题,持续更新共勉 在整个生命周期过程中,项目管理者需要始终坚持项目管理原则,通过涵盖 10 …...

Windows 操作系统 - Windows 10 磁盘管理无法为 C 盘选择扩展卷

Windows 10 磁盘管理无法为 C 盘选择扩展卷 在 Windows 10 的磁盘管理中,无法为 C 盘选择扩展卷(选项灰色不可用),主要原因是未分配空间没有紧邻 C 盘的右侧 补充:Windows 10 磁盘管理打开方式 1. 按下快捷键【Win …...

[NOIP 2003 普及组] 栈 Java

import java.io.*;public class Main {public static void main(String[] args) throws IOException {BufferedReader br new BufferedReader(new InputStreamReader(System.in));int n Integer.parseInt(br.readLine());int[] dp new int[n 1];dp[0] 1; // 空序列只有一种…...

C++ (类的设计,对象的创建,this指针,构造函数)

类的设计 C对结构体是有增强的 可以包含函数作为结构体成员 可以直接定义变量 在结构体成员函数里面可以直接访问结构体成员变量 struct student{string name;int age;float score;void play_game(const string &name);}void student::play_game(const string game){}…...

笔记:代码随想录算法训练营day67:Floyd 算法精讲、A * 算法精讲 (A star算法) 严重超时完结,不过,撒花

学习资料:代码随想录 Floyd 算法精讲 卡码网:97. 小明逛公园 首先明确floyd算法解决的是多源最短路径问题,对边的权的正负值没有要求,而且是动态规划的思想 五部曲: 定义:grid[i][j][k]表示从i出发到j…...

面试篇 - Transformer模型中的位置编码

1. 位置编码的引入 背景:Transformer模型通过自注意力机制(Self-Attention)处理序列数据,但自注意力机制本身并不包含序列中元素的位置信息。因此,需要一种方法来为模型提供位置信息。 解决方案:位置编码&…...

蓝桥杯篇---客观题

文章目录 前言 前言 本文简单介绍了蓝桥杯中客观题各个部分的知识点。 一、单片机相关 IAP15F2K61S2单片机的定时器0具有4种工作模式,当采用外部12MHz晶振时,定时器最大定时长度65535us。8051单片机的P0口,当使用外部存储器时它是一个传输低…...

ES6 新增特性 箭头函数

简述: ECMAScript 6(简称ES6)是于2015年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2015(ES2015)。它的目标是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语…...

Javaweb后端 maven高级 maven聚合

聚合用modules...

vue+flask图书知识图谱推荐系统

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站,有好处! 编号: F025 架构: vueflaskneo4jmysql 亮点:协同过滤推荐算法知识图谱可视化 支持爬取图书数据,数据超过万条&am…...

vue2 走马灯 展示多个

使用 npm install “swiper”: “^11.2.4”, 在这里插入代码片 <template><section class"swiper pc-banner"><div class"swiper-container"><div class"swiper-wrapper"><div v-for"(item, index) in swiperD…...

《MySQL从入门到精通》

文章目录 《MySQL从入门到精通》1. 基础-SQL通用语法及分类2. 基础-SQL-DDL-数据库操作3. 基础-SQL-DDL-表操作-创建&查询4. 基础-SQL-DDL-数据类型及案例4.1 数值类型4.2 字符串类型4.3 时间和日期类型 5. 基础-SQL-DDL-表操作-修改&删除5.1 DDL-表操作-修改5.2 DDL-表…...

Linux: 线程同步

目录 一 前言 二 线程饥饿 三 线程同步 四 条件变量 1. cond &#xff08; condition&#xff09; 2. pthread_cond_wait() &#xff1a; 3. pthread_cond_signal() 五 条件变量的使用 一 前言 在上篇文章Linux : 多线程互斥-CSDN博客我们讲解了线程互斥的概念&#xff…...

golang-context详解

Context是什么 cancel 其实就是通过chan select进行提前中断返回 如果没有context&#xff0c;携程之间怎么做这些交互呢&#xff1f;肯定也能做 跨线程通讯如共享内存&#xff0c;pipe等等都可以做到&#xff0c;但是就需要开发者对通讯设计建模、规划数据同步方式等&#xf…...

python蓝桥杯备赛常用算法模板

一、python基础 &#xff08;一&#xff09;集合操作 s1 {1,2,3} s2{3,4,5} print(s1|s2)#求并集 print(s1&s2)#求交集 #结果 #{1, 2, 3, 4, 5} #{3}&#xff08;二&#xff09;对多维列表排序 1.新建列表 list1[[1,2,3],[2,3,4],[0,3,2]] #提取每个小列表的下标为2的…...

Spring Boot 集成 RocketMQ 全流程指南:从依赖引入到消息收发

前言 在分布式系统中&#xff0c;消息中间件是解耦服务、实现异步通信的核心组件。RocketMQ 作为阿里巴巴开源的高性能分布式消息中间件&#xff0c;凭借其高吞吐、低延迟、高可靠等特性&#xff0c;成为企业级应用的首选。而 Spring Boot 通过其“约定优于配置”的设计理念&a…...

AI与我共创WEB界面

记录一次压测后的自我技术提升 这事儿得从机房停电说起。那天吭哧吭哧做完并发压测,正准备截Zabbix监控图写报告,突然发现监控曲线神秘失踪——系统组小哥挠着头说:“上次停电后,zabbix服务好像就没起来过…” 我盯着空荡荡的图表界面,大脑的CPU温度可能比服务器还高。 其…...

基于 `Gradio` 的聊天机器人界面

这段代码实现了一个基于 Gradio 的聊天机器人界面&#xff0c;并使用了 langchain 和 ChatGLM 作为后端模型支持。以下是对代码的详细解释&#xff1a; 1. 导入必要的库 import gradio as grfrom langchain_community.llms import ChatGLM from langchain.chains import Conve…...

基于频率约束条件的最小惯量需求评估,包括频率变化率ROCOF约束和频率最低点约束matlab/simulink

基于频率约束条件的最小惯量评估&#xff0c;包括频率变化率ROCOF约束和频率最低点约束matlab/simulink 1建立了含新能源调频的频域仿真传函模型&#xff0c;虚拟惯量下垂控制 2基于构建的模型&#xff0c;考虑了不同调频系数&#xff0c;不同扰动情况下的系统最小惯量需求...

【spark-submit】--提交任务

Spark-submit spark-submit 是 Apache Spark 提供的用于提交 Spark 应用程序到集群的命令行工具。 基本语法 spark-submit [options] <app-jar> [app-arguments]常用参数说明 应用程序配置 --class <class-name>: 指定应用程序的主类&#xff08;对于 Java/Sc…...