【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

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

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

相关文章

【ROS2】行为树 BehaviorTree(四):组合使用子树

1、大树调用子树 如下图,左边为大树主干: 1)如果门没有关,直接通过; 2)如果门关闭了,执行开门动作,然后通过 右边为子树,主要任务是开门 1)尝试直接开门; 2)尝试开锁开门,最多尝试5次; 3)最后尝试砸门! XML如何描述大树主干调佣子树:使用关键字 SubTree 来…

【口腔粘膜鳞状细胞癌】文献阅读

写在前面 看看文章,看看有没有思路 文献 The regulatory role of cancer stem cell marker gene CXCR4 in the growth and metastasis of gastric cancer IF:6.8 中科院分区:1区 医学WOS分区: Q1 目的:通过 scRNA-seq 结合大量 RNA-seq 揭示癌症干细胞…

【ComfyUI】蓝耘元生代 | ComfyUI深度解析:高性能AI绘画工作流实践

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈人工智能与大模型应用 ⌋ ⌋ ⌋ 人工智能(AI)通过算法模拟人类智能,利用机器学习、深度学习等技术驱动医疗、金融等领域的智能化。大模型是千亿参数的深度神经网络(如ChatGPT&…

深入理解Java中的队列:核心操作、实现与应用

队列(Queue)是计算机科学中最基础且重要的数据结构之一,遵循 先进先出(FIFO) 的规则。Java通过java.util.Queue接口及其丰富的实现类为开发者提供了强大的队列工具。本文将详细解析Java队列的核心操作、常见实现类及其…

idea里面不能运行 node 命令 cmd 里面可以运行咋回事啊

idea里面不能运行 node 命令 cmd 里面可以运行咋回事啊 在 IntelliJ IDEA(或其他 JetBrains 系列 IDE)中无法运行某些命令,但在系统的命令提示符(CMD)中可以正常运行,这种情况通常是由于以下原因之一导致的…

Express学习笔记(六)——前后端的身份认证

目录 1. Web 开发模式 1.1 服务端渲染的 Web 开发模式 1.2 服务端渲染的优缺点 1.3 前后端分离的 Web 开发模式 1.4 前后端分离的优缺点 1.5 如何选择 Web 开发模式 2. 身份认证 2.1 什么是身份认证 2.2 为什么需要身份认证 2.3 不同开发模式下的身份认证 3. Sessio…

微服务与Spring Cloud Alibaba简介

微服务(或微服务架构)是一种云原生架构方法,其中单个应用程序由许多松散耦合且可独立部署的较小组件或服务组成。本单元主要介绍微服务架构的定义、微服务的特征、微服务架构面临的挑战、Spring Cloud 定义、Spring Cloud 核心组件、Spring C…

JPG同步删除RAW批处理文件

相机挑选JPG照片,同步删除RAW格式文件,批处理文件bat,放到JPG和NEF文件夹根目录 – NEF 文件夹 – JPG 文件夹 文件同步删除.bat echo off:: 要同步的文件夹及文件后缀名(相同),即要删除文件的目录 set de…

InnoDB的MVCC实现原理?MVCC如何实现不同事务隔离级别?MVCC优缺点?

概念 InnoDB的MVCC(Multi-Version Concurrency Control)即多版本并发控制,是一种用于处理并发事务的机制。它通过保存数据在不同时间点的多个版本,让不同事务在同一时刻可以看到不同版本的数据,以此来减少锁竞争&…

针对 Java从入门到精通 的完整学习路线图、各阶段技术点、CTO进阶路径以及经典书籍推荐。内容分阶段展开,兼顾技术深度与职业发展

以下是针对 Java从入门到精通 的完整学习路线图、各阶段技术点、CTO进阶路径以及经典书籍推荐。内容分阶段展开,兼顾技术深度与职业发展。 一、学习路线图分阶段详解 阶段1:Java基础入门(3-6个月) 目标:掌握Java核心…

报错:Nlopt

报错:Nlopt CMake Error at TGH-Planner/fast_planner/bspline_opt/CMakeLists.txt:20 (find_package):By not providing "FindNLopt.cmake" in CMAKE_MODULE_PATH this project hasasked CMake to find a package configuration file provided by "…

鸿蒙公共通用组件封装实战指南:从基础到进阶

一、鸿蒙组件封装核心原则 1.1 高内聚低耦合设计 在鸿蒙应用开发中,高内聚低耦合是组件封装的关键准则,它能极大提升代码的可维护性与复用性。 从原子化拆分的角度来看,我们要把复杂的 UI 界面拆分为基础组件和复合组件。像按钮、输入框这…

Linux 网络基础二 ——应用层HTTP\HTTPS协议

我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层。 前面写的套接字接口都是传输层经过对 UDP 和 TCP 数据发送能力的包装,以文件的形式呈现给我们,让我们可以进行应用层编程。换而言之&#xff0c…

Spark-SQL

Spark-SQL 概述 Spark SQL 是 Spark 用于结构化数据(structured data)处理的 Spark 模块 Shark 是伯克利实验室 Spark 生态环境的组件之一,是基于 Hive 所开发的工具,它修改了内存管理、物理计划、执行三个模块,并使之能运行在 Spark 引擎上…

Java 在人工智能领域的突围:从企业级架构到边缘计算的技术革新

一、Java AI 的底层逻辑:从语言特性到生态重构 在 Python 占据 AI 开发主导地位的当下,Java 正通过技术重构实现突围。作为拥有 30 年企业级开发经验的编程语言,Java 的核心优势在于强类型安全、内存管理能力和分布式系统支持,这…

编程实现除法程序时需要注意的细节

使用Python实现除法程序时,需注意以下关键细节: 除数为零的处理 必须检查除数是否为零,否则会触发ZeroDivisionError异常。可通过try-except结构捕获异常并处理。 整数除法与浮点数除法的区别 • 使用/运算符时,无论操作数是否为…

Java万级并发场景-实战解决

今天我们来做一个典型的消费力度能达到万级别的并发场景,老师点名-学生签到 正常情况 正常情况来说是不同班级下的老师发布不同的点名--然后不同班级下的很多学生同一时间进行签到,签到成功就去修改数据库,签到失败就返回,但是这…

openGauss新特性 | 自动参数化执行计划缓存

目录 自动化参数执行计划缓存简介 SQL参数化及约束条件 一般常量参数化示例 总结 自动化参数执行计划缓存简介 执行计划缓存用于减少执行计划的生成次数。openGauss数据库会缓存之前生成的执行计划,以便在下次执行该SQL时直接使用,可…

计算机操作系统——存储器管理

系列文章目录 1.存储器的层次结构 2.程序的装入和链接 3.连续分配存储管理方式(内存够用) 4.对换(Swapping)(内存不够用) 5.分页存储管理方式 6.分段存储管理方式 文章目录 系列文章目录前言一、存储器的存储结构寄存器&…

KF V.S. GM-PHD

在计算机视觉的多目标跟踪(MOT)任务中,卡尔曼滤波(KF)和高斯混合概率假设密度(GM-PHD)滤波器是两种经典的状态估计方法,但它们的原理和应用场景存在显著差异。以下是两者的核心机制和…