【Redis | 第四篇】基于布隆过滤器解决Redis穿透问题

news2024/12/25 2:39:11

文章目录

  • 4.基于布隆过滤器解决Redis穿透问题
    • 4.1什么是redis的穿透问题
    • 4.2解决穿透问题
    • 4.3布隆过滤器
      • 4.3.1思想
      • 4.3.2特点
      • 4.3.3缺点
    • 4.4基于Springboot实现布隆过滤器
      • 4.4.1导入依赖
      • 4.4.2yml配置
      • 4.4.3两个工具类
        • (1)BloomFilterHelper
        • (2)RedisBloomFilter
        • (3)将RedisBloomFilter装配到容器中
      • 4.4.4使用RedisBloomFilter
      • 4.4.5初始添加数据
      • 4.4.6测试
    • 4.5难点
    • 4.6小结

4.基于布隆过滤器解决Redis穿透问题

4.1什么是redis的穿透问题

查询一个在数据库不存在的数据,redis不会将这个数据进行缓存,导致每一次查询都要到数据库中查询,增加数据库压力

4.2解决穿透问题

  1. 缓存空值:对于后端存储中不存在的数据,可以将其在缓存中设置为空值缓存,即存储一个特殊的空值标识。这样,在下一次查询该数据时,即使缓存中为空值标识,也可以避免请求直接穿透到后端存储系统。
  2. 布隆过滤器:使用布隆过滤器可以在缓存层面进行快速的数据存在性检查。布隆过滤器是一种概率型的数据结构,可以高效地判断一个元素是否可能存在于集合中,通过在缓存层进行预先判断,可以防止不存在的数据请求直接穿透到后端存储系统。

4.3布隆过滤器

4.3.1思想

布隆过滤器是一种空间效率高、时间效率快的数据结构,主要用于判断一个元素是否存在于一个集合中

其基本思想是 通过多个哈希函数将元素映射到一个比特数组 中,并根据哈希函数的结果设置相应的比特位。

当需要判断一个元素是否存在于集合中时,布隆过滤器会使用相同的哈希函数计算出相应的比特位,并检查这些位置上的比特值。如果所有对应的比特位都为 1,则可以判断元素很可能在集合中;如果有任何一个比特位为 0,则可以断定元素一定不在集合中。

举例:

img

4.3.2特点

  1. 空间效率高:布隆过滤器只需要存储少量的比特位信息,所以占用的空间远远小于存储实际元素的空间。

  2. 时间效率快:判断一个元素是否存在于集合中只需要计算几个哈希函数并检查对应的比特位,所以查询速度非常快。

  3. 存在误判:因为多个元素可能映射到相同的比特位上,所以布隆过滤器可能会出现误判,即判断一个元素在集合中但实际并不存在。

  4. 布隆过滤器的误判率可以通过哈希函数的数量来进行调整

    哈希函数数量越多,误判率越低!而哈希函数越多,所映射出来的下标值就越多,下标值越多,一维数组的长度就越长,布隆过滤器的空间复杂度就越高。

4.3.3缺点

  1. 存在误判:布隆过滤器的设计初衷是为了提高查询效率,但在实际应用中可能会出现误判,即判断某个元素在集合中但实际并不在。这是因为多个元素经过哈希函数映射后可能会落在相同的比特位上,导致冲突。误判的发生会根据布隆过滤器的大小和元素数量等因素而不同。
  2. 无法删除元素:由于布隆过滤器的设计是基于多个哈希函数映射到固定大小的比特数组上,所以无法直接删除已经添加的元素因为删除一个元素可能会影响到其他元素映射到的比特位,从而导致误判的增加。如果需要实现删除操作,通常需要重新构建一个新的布隆过滤器。
  3. 需要合适的哈希函数:布隆过滤器的性能与哈希函数的选择密切相关,需要选择足够独立且分布均匀的哈希函数,以降低冲突率和误判率。
  4. 不适合小数据量:布隆过滤器对于小数据量的情况下,可能会浪费较大的空间,因为需要维护一个较大的比特数组。在数据量较小时,使用传统的数据结构如哈希表可能更为合适。

4.4基于Springboot实现布隆过滤器

使用Google Guava中提供的BloomFilter,布隆过滤器

4.4.1导入依赖

        <!--使用Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--借助guava的布隆过滤器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>

4.4.2yml配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 12345
    jedis.pool.max-idle: 100 	#连接池中最大空闲连接数
    jedis.pool.max-wait: -1ms	#连接池中获取连接的最大等待时间,-1表示无限等待
    jedis.pool.min-idle: 2		#连接池中最小空闲连接数
    timeout: 2000ms				#连接redis的超时时间

4.4.3两个工具类

(1)BloomFilterHelper

BloomFilterHelper 类中的方法主要用于根据期望插入元素的数量和假阳性率计算 Bloom Filter 的比特数组长度和哈希函数个数,并 生成一组哈希偏移量,用于设置比特数组中的比特位。

package com.whrfjd.rescenter.utis;

import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;

public class BloomFilterHelper<T> {

    private int numHashFunctions;

    private int bitSize;

    private Funnel<T> funnel;

    /**
    *	构造函数,用于初始化 BloomFilterHelper 对象。
    *	funnel 参数是用于将对象转换成比特数组的 Funnel 类型对象,expectedInsertions 参数是期望插入元素的数量,fpp 参数是		*	假阳性率(False Positive Probability)
    */
    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
        Preconditions.checkArgument(funnel != null, "funnel不能为空");
        this.funnel = funnel;
        // 计算bit数组长度
        bitSize = optimalNumOfBits(expectedInsertions, fpp);
        // 计算hash方法执行次数
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    /*
    *	根据给定的值生成一组哈希偏移量。该方法接受一个泛型类型的参数 value,并返回一个整型数组
    *	数组包含了多个哈希偏移量,用于确定在布隆过滤器的比特数组中哪些位置需要设置为 1。
    */
    public int[] murmurHashOffset(T value) {
        int[] offset = new int[numHashFunctions];

        long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
        int hash1 = (int) hash64;
        int hash2 = (int) (hash64 >>> 32);
        for (int i = 1; i <= numHashFunctions; i++) {
            int nextHash = hash1 + i * hash2;
            if (nextHash < 0) {
                nextHash = ~nextHash;
            }
            offset[i - 1] = nextHash % bitSize;
        }
        return offset;
    }

    /**
     * 计算bit数组长度:根据期望插入元素的数量和假阳性率计算布隆过滤器的比特数组长度。
     */
    private int optimalNumOfBits(long n, double p) {
        if (p == 0) {
            // 设定最小期望长度
            p = Double.MIN_VALUE;
        }
        int sizeOfBitArray = (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
        return sizeOfBitArray;
    }

    /**
     * 计算hash方法执行次数:根据期望插入元素的数量和比特数组长度计算需要执行的哈希函数个数
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        int countOfHash = Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
        return countOfHash;
    }
}
(2)RedisBloomFilter

RedisBloomFilter该类通过使用 BloomFilterHelper 和 RedisTemplate,在 Redis 中实现了布隆过滤器的添加和查询功能

在添加大量数据时,可以使用布隆过滤器来快速排除不需要的数据,从而减少查询开销。

package com.whrfjd.rescenter.utis;

import com.google.common.base.Preconditions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;


@Service
public class RedisBloomFilter {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据给定的布隆过滤器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);	//使用 bloomFilterHelper 对象的 murmurHashOffset() 方法为给定的值生成一组哈希偏移量。这些偏移量将用于确定在布隆过滤器的比特数组中哪些位置需要设置为 1。
        for (int i : offset) {
            System.out.println("key : " + key + " " + "value : " + i);
            redisTemplate.opsForValue().setBit(key, i, true);	//通过迭代遍历生成的偏移量数组,并使用 redisTemplate.opsForValue().setBit() 方法将对应的比特位设置为 1。具体地,它会将指定 key 对应的 Redis 字符串值的二进制位中的偏移量位置设置为 true。
        }
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        // 使用 Preconditions 进行参数校验,确保 bloomFilterHelper 不为 null
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        
        // 根据给定的值计算哈希偏移量
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            System.out.println("key : " + key + " " + "value : " + i);
            
             // 判断 Redis 中对应位置的比特值是否为 1
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }

        // 如果所有位置的比特值都为 1,则返回 true,表示值可能存在于布隆过滤器中
        return true;
    }

}
(3)将RedisBloomFilter装配到容器中
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class RedisBloomFilterConfig {
 
    //  初始化布隆过滤器,放入到spring容器里面
    @Bean
    public BloomFilterHelper<String> initBloomFilterHelper() {
        return new BloomFilterHelper<>((Funnel<String>) (from, into) -> into.putString(from, Charsets.UTF_8).putString(from, Charsets.UTF_8), 1000000, 0.01);
    }
    
    @Bean
    public RedisBloomFilter{
        return new RedisBloomFilter();
    }
}

4.4.4使用RedisBloomFilter

首先在controller层注入redisBloomFilrer以及bloomFilterHelper

	@Autowired
    private RedisBloomFilter redisBloomFilter;

    @Autowired
    private BloomFilterHelper bloomFilterHelper;

4.4.5初始添加数据

  • 需要手动发送请求
 	 @GetMapping("/redis/bloomFilter")
	 public ResponseResult redisBloomFilter(){
        List<String> allResourceId = resCenterDao.getAllResourceId();
        for (String id : allResourceId) {
            //将所有的资源id放入到布隆过滤器中
            redisBloomFilter.addByBloomFilter(bloomFilterHelper,"bloom",id);
        }
        return new ResponseResult(ResponseEnum.SUCCESS);
    }

4.4.6测试

 	@GetMapping("/redis/bloomFilter/resourceId")
    @ApiOperation("redis布隆过滤器资源测试")
    public ResponseResult redisBloomFilterResourceId(@RequestParam("resourceId")String resourceId){
        boolean mightContain = redisBloomFilter.includeByBloomFilter(bloomFilterHelper,"bloom",resourceId);
        if (!mightContain){
            return new QueryResult<>(ResCenterEnum.RESOURCE_EXSIT,"");
        }
        return new ResponseResult(ResponseEnum.SUCCESS);
    }

4.5难点

布隆过滤器的使用难点主要在于:如何在真实业务海量数据的背景下。实现对布隆过滤器的初始化,因为我们要直接从数据库中拿数据,这种大规模的IO操作势必会给服务器来带很大的压力

  • 解决:
  1. 分批次初始化:创建定时任务,分批存储数据到布隆过滤器中。
  2. 利用多线程并发处理:可以开启多个线程来并发地读取和处理数据,以提高初始化速度。需要注意的是,在并发处理时需要保证线程安全,避免出现数据竞争等问题。
  3. 选择适合的哈希函数和比特数组大小:在初始化时,需要根据实际数据大小和误判率要求来选择合适的哈希函数和比特数组大小。较小的比特数组大小和合适的哈希函数可以减少初始化所需的空间和时间。

4.6小结

在解决Redis穿透问题时,布隆过滤器是一种非常有效的工具。它能够在缓存层面进行快速的数据存在性检查,从而避免了不存在的数据请求直接穿透到后端存储系统,减轻了数据库的压力。通过布隆过滤器的特点和使用方法的介绍,我们了解到它可以高效地判断一个元素是否可能存在于集合中,同时也存在一定的误判率和一些实际应用上的限制。

基于Spring Boot实现布隆过滤器需要依赖Google Guava中提供的BloomFilter,并结合Redis进行布隆过滤器的添加和查询功能。在实际使用中,我们可以通过BloomFilterHelper类和RedisBloomFilter类来完成对布隆过滤器的初始化和使用,从而应对真实业务海量数据的挑战。

在使用布隆过滤器时,需要考虑如何进行布隆过滤器的初始化,特别是在海量数据背景下,这可能会给服务器带来很大的压力。针对这个难点,我们可以采取分批次初始化、利用多线程并发处理以及选择适合的哈希函数和比特数组大小等策略来解决。

总的来说,布隆过滤器是一种强大的工具,可以在一定程度上解决Redis穿透问题,提高系统的性能和稳定性。

但是,前面已经介绍过了布隆过滤器的缺点:无法删除元素,内存效率低;在接下来的内容中,我们将继续介绍 另一种高效的过滤器——布谷鸟过滤器

布谷鸟过滤器(Cuckoo Filter)相对于布隆过滤器具有一些优点和缺点。

  • 优点:

    1. 删除支持:布谷鸟过滤器支持删除操作,而布隆过滤器不支持删除。这意味着可以从布谷鸟过滤器中安全地删除元素,而不会对其他元素的存在性判断造成影响。
    2. 内存效率高:相比布隆过滤器,在相同的误判率下,布谷鸟过滤器通常能够使用更少的内存空间。
  • 缺点:

    1. 性能:在某些情况下,布谷鸟过滤器的性能可能略逊于布隆过滤器,特别是在处理大规模数据时,布隆过滤器可能更快速。

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

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

相关文章

李沐动手学习深度学习——4.2练习

1. 在所有其他参数保持不变的情况下&#xff0c;更改超参数num_hiddens的值&#xff0c;并查看此超参数的变化对结果有何影响。确定此超参数的最佳值。 通过改变隐藏层的数量&#xff0c;导致就是函数拟合复杂度下降&#xff0c;隐藏层过多可能导致过拟合&#xff0c;而过少导…

KubeSphere平台安装系列之二【Linux单节点部署KubeSphere】(2/3)

**《KubeSphere平台安装系列》** 【Kubernetes上安装KubeSphere&#xff08;亲测–实操完整版&#xff09;】&#xff08;1/3&#xff09; 【Linux单节点部署KubeSphere】&#xff08;2/3&#xff09; 【Linux多节点部署KubeSphere】&#xff08;3/3&#xff09; **《KubeS…

Matlab 机器人工具箱 运动学

文章目录 R.fkine()R.ikine()R.ikine6s()R.jacob0、R.jacobn、R.jacob_dotjtrajctraj参考链接官网:Robotics Toolbox - Peter Corke R.fkine() 正运动学,根据关节坐标求末端执行器位姿 mdl_puma560; % 加载puma560模型 qz % 零角度 qr

备战蓝桥杯---状态压缩DP基础2之TSP问题

先来一个题衔接一下&#xff1a; 与上一题的思路差不多&#xff0c;不过这里有几点需要注意&#xff1a; 1.因为某一列的状态还与上上一行有关&#xff0c;因此我们令f[i][j][k]表示第i行状态为j,第i-1行状态为k的最大炮兵数。 因此&#xff0c;我们可以得到状态转移方程&…

IDEA的安装教程

1、下载软件安装包 官网下载&#xff1a;https://www.jetbrains.com/idea/ 2、开始安装IDEA软件 解压安装包&#xff0c;找到对应的idea可执行文件&#xff0c;右键选择以管理员身份运行&#xff0c;执行安装操作 3、运行之后&#xff0c;点击NEXT&#xff0c;进入下一步 4、…

电子科技大学《数据库原理及应用》(持续更新)

前言 电子科技大学的数据库课程缩减了部分的课时&#xff0c;因此&#xff0c;可能并不适合所有要学习数据库的宝子们&#xff0c;但是&#xff0c;本人尽量将所有数据库的内容写出来。本文章适用于本科生的期中和期末的复习&#xff0c;电子科技大学的考生请在复习前先看必读…

【算法】顺时针打印矩阵(图文详解,代码详细注释

目录 题目 代码如下: 题目 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。例如:如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则打印出数字:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10 这一道题乍一看,没有包含任何复杂的数据结构和…

Android Gradle开发与应用 (三) : Groovy语法概念与闭包

1. Groovy介绍 Groovy是一种基于Java平台的动态编程语言&#xff0c;与Java是完全兼容&#xff0c;除此之外有很多的语法糖来方便我们开发。Groovy代码能够直接运行在Java虚拟机&#xff08;JVM&#xff09;上&#xff0c;也可以被编译成Java字节码文件。 以下是Groovy的一些…

驾辰龙跨Llama持Wasm,玩转Yi模型迎新春

今年新年很特别&#xff0c;AI工具添光彩。今天就来感受下最新的AI神器天选组合“WasmEdgeYi-34B”&#xff0c;只要短短三步&#xff0c;为这个甲辰龙年带来一份九紫离火运的科技感。 环境准备 这次用的算力是OpenBayes提供的英伟达RTX_4090*1、24GB显存、20核CPU、80GB内存…

【论文笔记】Mamba:挑战Transformer地位的新架构

Mamba Mamba: Linear-Time Sequence Modeling with Selective State Spaces Mamba Mamba摘要背景存在的问题本文的做法实验结果 文章内容Transformer的缺点Structured state space sequence models (SSMs)介绍本文的工作模型介绍State Space ModelsSelective State Space Mod…

docker三剑客compose+machine+swarm小结

背景 在容器领域&#xff0c;不少公司会使用docker三剑客composemachineswarm进行容器编排和部署&#xff0c;本文就简单记录下这几个工具的用法 三剑客composemachineswarm compose compose主要是用于容器编排&#xff0c;我们部署容器时&#xff0c;容器之间会有依赖&…

git的安装、使用

文章目录 安装gitgit学习网站git初始配置具体配置信息 新建版本库&#xff08;仓库&#xff09;git的工作区域和文件状态工作区域文件状态git文件提交的基础指令 git基础指令1. 版本提交2. 分支创建3. 分支切换4. 分支合并(1) git merge(2) git rebase 5. 在git的提交树上移动(…

vue+springboot项目部署服务器

项目仓库&#xff1a;vuespringboot-demo: vuespringboot增删改查的demo (gitee.com) ①vue中修改配置 在public文件夹下新建config.json文件&#xff1a; {"serverUrl": "http://localhost:9090"//这里localhost在打包后记得修改为服务器公网ip } 然后…

三天学会阿里分布式事务框架Seata-seata事务日志mysql持久化配置

锋哥原创的分布式事务框架Seata视频教程&#xff1a; 实战阿里分布式事务框架Seata视频教程&#xff08;无废话&#xff0c;通俗易懂版&#xff09;_哔哩哔哩_bilibili实战阿里分布式事务框架Seata视频教程&#xff08;无废话&#xff0c;通俗易懂版&#xff09;共计10条视频&…

Java中的Collection

Collection Collection 集合概述和使用 Collection集合概述 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素 JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现 创建Collection集合的对象 多态的方式 具体的实现类ArrayList C…

Pycharm的下载安装与汉化

一.下载安装包 1.接下来按照步骤来就行 2.然后就能在桌面上找到打开了 3.先建立一个文件夹 二.Pycharm的汉化

javaweb day9 day10

昨天序号标错了 vue的组件库Elent 快速入门 写法 常见组件 复制粘贴 打包部署

修改一个教材上的网站源码使它能在www服务器子目录上正常运行

修改一个教材上的网站源码&#xff0c;使它能在www服务器子目录上正常运行。 该网站源码是教材《PHPMySQL网站开发项目式教程》上带的网站源码。该源码是用 php html 写的。该源码包含对mysql数据库进行操作的php代码。以前该网站源码只能在www服务器的根目录上正常运行&…

一文认识蓝牙(验证基于Aduino IDE的ESP32)

1、简介 蓝牙技术是一种无线通信的方式&#xff0c;利用特定频率的波段&#xff08;2.4GHz-2.485GHz左右&#xff09;&#xff0c;进行电磁波传输&#xff0c;总共有83.5MHz的带宽资源。 1.1、背景 蓝牙&#xff08;Bluetooth&#xff09;一词取自于十世纪丹麦国王哈拉尔Haral…

[技巧]Arcgis之图斑四至点批量计算

前言 上一篇介绍了arcgis之图斑四至范围计算&#xff0c;这里介绍的图斑四至点的计算及获取&#xff0c;两者之间还是有差异的。 [技巧]Arcgis之图斑四至范围计算 这里说的四至点指的是图斑最东、最西、最南、最北的四个地理位置点坐标&#xff0c;如下图&#xff1a; 四至点…