电商高并发设计之SpringBoot整合Redis实现布隆过滤器

news2024/11/17 13:21:24

在这里插入图片描述

文章目录

  • 问题背景
  • 前言
  • 布隆过滤器原理
  • 使用场景
  • 基础中间件搭建
  • 如何实现布隆过滤器
    • 引入依赖
    • 注入RedisTemplate
    • 布隆过滤器核心代码
    • Redis操作布隆过滤器
    • 验证
  • 总结

问题背景

研究布隆过滤器的实现方式以及使用场景

前言

  1. 本篇的代码都是参考SpringBoot+Redis布隆过滤器防恶意流量击穿缓存的正确姿势,可以先看看该篇文章。
  2. 本篇的行文思路分别由以下几个模块构成:布隆过滤器原理、使用场景、基础中间件搭建、如何实现布隆过滤器
  3. 阅读本篇前,需要知道布隆过滤器的原理、简单的Docker知识,阅读起来能更加高效。

布隆过滤器原理

在这里插入图片描述

存储原理:有一个value,一个数组array,value通过k个hash函数得到k个值,这k个值映射到数组array上并将数组对应位置的值设置为1。
判断原理:一个value通过k个hash函数得到的值,如果在array数组上的位置不全是1,则该value不存在。

总结:布隆过滤器能断定某个value一定不存在,无法断定某个value一定存在。

使用场景

  1. 防止恶意请求导致缓存穿透。(缓存雪崩:一堆key同时过期;缓存击穿:热点key过期;缓存穿透:缓存和数据库都没有该key)
  2. 在大批量数据场景下做内容去重。比如爬虫的url去重,亿级量账号去重,垃圾邮箱过滤等等。

基础中间件搭建

本文采用Redis实现,需要搭建Redis,由于是用来做实验的,不需要纠结怎么安装Redis才算正确,直接用Docker快速搭建一个含有布隆过滤器模块的Redis。代码见如下:

在此前需要先安装Docker,网上很多教程,此处不做赘述

# 开启docker服务
systemctl enable docker
# 直接拉取整合了bloomfilter插件的Redis镜像
docker pull redislabs/rebloom
# 启动
docker run -p 6379:6379 -d --name redisbloom redislabs/rebloom
# 进入容器
docker exec -it redis-redisbloom bash
# 进入命令行测试
redis-cli
# 添加一个过滤器与记录
BF.ADD newFilter foo
# 判断记录是否存在
BF.EXISTS newFilter foo

搭建完后,可以采用RedisInsight可视化软件查看Redis的一些情况,如下所示:

在这里插入图片描述

如何实现布隆过滤器

核心代码是如何使用redis存储值、判断值是否存在。本小节先给出配置类的代码、再给出布隆过滤器核心算法的代码(3要素,hash函数的数量,bit数组的大小,误判率)、redis操作布隆过滤器的代码。

下面的代码是防止恶意请求的一个使用例子,用于判断请求是否存在于
布隆过滤器中,不存在则可以直接return,不继续浪费服务器资源。

引入依赖

引入Redis、guava依赖,redis版本号跟着springboot版本号走

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>31.1-jre</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>${spring-boot.version}</version>
</dependency>

注入RedisTemplate

package com.ganzalang.gmall.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisStandaloneConfig {

    /**
     * retemplate相关配置
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(factory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 值采用json序列化
        template.setValueSerializer(jacksonSeial);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 对hash类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 对redis字符串类型数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 对链表类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 对无序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 对有序集合类型的数据操作
     *
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

}

布隆过滤器核心代码

package com.ganzalang.gmall.redis.bloomfilter.util;

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;

    public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
        Preconditions.checkArgument(funnel !=  null, "funnel不能为空");
        this.funnel = funnel;
        this.bitSize = optimalNumOfBits(expectedInsertions, fpp);
        this.numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
    }

    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数组的大小
     *
     * @param n
     * @param p
     * @return
     */
    private int optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }

    /**
     * 计算Hash方法执行次数
     *
     * @param n
     * @param m
     * @return
     */
    private int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }
}

Redis操作布隆过滤器

package com.ganzalang.gmall.redis.bloomfilter.util;


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

import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 默认过期时长,单位:秒
     */
    public static final long DEFAULT_EXPIRE = 60 * 60 * 24;

    /**
     * 不设置过期时长
     */
    public static final long NOT_EXPIRE = -1;

    public boolean existsKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 重名名key,如果newKey已经存在,则newKey的原值被覆盖
     *
     * @param oldKey
     * @param newKey
     */
    public void renameKey(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * newKey不存在时才重命名
     *
     * @param oldKey
     * @param newKey
     * @return 修改成功返回true
     */
    public boolean renameKeyNotExist(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 删除key
     *
     * @param key
     */
    public void deleteKey(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 删除多个key
     *
     * @param keys
     */
    public void deleteKey(String... keys) {
        Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 删除Key的集合
     *
     * @param keys
     */
    public void deleteKey(Collection<String> keys) {
        Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 设置key的生命周期
     *
     * @param key
     * @param time
     * @param timeUnit
     */
    public void expireKey(String key, long time, TimeUnit timeUnit) {
        redisTemplate.expire(key, time, timeUnit);
    }

    /**
     * 指定key在指定的日期过期
     *
     * @param key
     * @param date
     */
    public void expireKeyAt(String key, Date date) {
        redisTemplate.expireAt(key, date);
    }

    /**
     * 查询key的生命周期
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public long getKeyExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 将key设置为永久有效
     *
     * @param key
     */
    public void persistKey(String key) {
        redisTemplate.persist(key);
    }

    /**
     * 根据给定的布隆过滤器添加值
     */
    public <T> void addByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            redisTemplate.opsForValue().setBit(key, i, true);
        }
    }

    /**
     * 根据给定的布隆过滤器判断值是否存在
     */
    public <T> boolean includeByBloomFilter(BloomFilterHelper<T> bloomFilterHelper, String key, T value) {
        Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
        int[] offset = bloomFilterHelper.murmurHashOffset(value);
        for (int i : offset) {
            if (!redisTemplate.opsForValue().getBit(key, i)) {
                return false;
            }
        }

        return true;
    }

}

验证

保存请求,如下图所示,发送POST /user/addEmailToBloom请求:

在这里插入图片描述

判断请求,如下图所示,发送POST /user/checkEmailInBloom请求:

在这里插入图片描述

总结

  • 效果:上面代码中,可以看见底层使用的是redis的bitmap,120w数据存在Redis仅需8M。查询一次仅需几十毫秒。
  • 优点:空间效率高;;查询时间快;支持高并发。
  • 缺点:存在一定的误判率;不支持元素的删除,如需删除,需要重新构建布隆过滤器。
  • 使用场景:在生产环境中,可以先把数据加载进布隆过滤器,然后用来做防穿透或者去重。

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

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

相关文章

让数据管理由繁至简的低代码开发平台

随着社会数字化能力的快速升级&#xff0c;各行各业正逐渐迈向数字化转型的新时代。尤其是AI的爆发&#xff0c;数据智能技术正在彻底改变着这个行业的面貌&#xff0c;随着越来越多的企业开始将人工智能、机器学习和大数据分析技术应用到其业务中&#xff0c;数据的价值正在得…

深度解剖动态内存管理

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

【项目 进程8】 2.17 内存映射(1) 2.18内存映射(2)

文章目录 2.17 内存映射&#xff08;1&#xff09;内存映射内存映射相关系统调用使用内存映射实现父子进程间通信使用内存映射实现没有关系的进程间的通信 2.18内存映射&#xff08;2&#xff09;内存映射的注意事项使用内存映射实现内存拷贝的功能匿名映射 2.17 内存映射&…

【Spring】学习Spring需要掌握的核心设计思想

目录 一、Spring是什么 二、什么是IoC容器 1、什么是容器 2、什么是IoC 3、Spring IoC 4、DI&#xff08;依赖注入&#xff09; 4.1、IoC和DI的区别 5、 DL&#xff08;依赖查找&#xff09; 一、Spring是什么 我们通常所说的Spring指的是Spring Framework&#xff08;…

DAY55:单调栈(一)每日温度+下一个更大元素Ⅰ

文章目录 739.每日温度栈数据结构单调栈思路单调栈原理单调栈注意点判断条件工作过程分析 完整版 496.下一个更大元素Ⅰ思路映射思路 完整版总结 739.每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 an…

2023年第四届“华数杯”数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常检测 异常…

【Java】UWB高精度工业人员安全定位系统源码

基于VueSpring boot前后端分离架构开发的一套UWB技术高精度定位系统源码。 UWB高精度人员定位系统提供实时定位、电子围栏、轨迹回放等基础功能以及各种拓展功能,用户可根据实际需要任意选择搭配拓展功能。该系统简易部署&#xff0c;方便使用&#xff0c;实时响应。UWB高精度定…

Java on Azure Tooling 6月更新|标准消费和专用计划及本地存储账户(Azurite)支持

作者&#xff1a;Jialuo Gan - Program Manager, Developer Division at Microsoft 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎阅读 Java on Azure 工具的六月更新。在本次更新中&#xff0c;我们将介绍 Azure Spring Apps 标准消费和专用计划支持以及本地存储账户&…

二叉树迭代遍历

PS:以下代码均为C实现 1.二叉树前序遍历 力扣 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 class Solution { public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;vector<int> str;TreeNode* curroot;whil…

【波浪动态特效】基于jquery实现页面底部波浪动画效果(附完整源码下载)

文章目录 写在前面涉及知识点实现效果1、搭建页面1.1、创建两个片区1.2、创建波浪区域1.3、静态页面源码 2、JS实现波浪效果2.1 动画原理2.2 动画源码 3、源码分享3.1 百度网盘3.2 123云盘3.3 邮箱留言 总结 写在前面 想必搭建过企业官网的大多数对这个效果不陌生吧&#xff0…

备战秋招 | 笔试强训21

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、设一个有序的单链表中有n个结点&#xff0c;现要求插入一个新结点后使得单链表仍然保持有序&#xff0c;则该操作的时间复杂度&#xff08;&#xff09; A. O(log2n) B. O(1) C. O(n2) D. O(n) 2…

网络中通过IP地址查找位置

display ip routing-table 查看路由表 display vlan 查看vlan 信息 display stp brief 查看生成树信息 display mac-address 查看mac 地址表 display arp 查看arp表 SW1 SW2

海量小文件传输慢的原因以及对应的优化方案

在日常工作中&#xff0c;我们经常遇到需要传输一些小文件的情况&#xff0c;但是当小文件的数量很多的时候&#xff0c;为什么小文件传输的速度就会变得很慢呢&#xff1f;为什么复制许多较小的文件时&#xff0c;小文件传输效率就会降低呢&#xff1f;针对这些问题&#xff0…

MyBatis查询数据库(3)

前言&#x1f36d; ❤️❤️❤️SSM专栏更新中&#xff0c;各位大佬觉得写得不错&#xff0c;支持一下&#xff0c;感谢了&#xff01;❤️❤️❤️ Spring Spring MVC MyBatis_冷兮雪的博客-CSDN博客 前面我们讲解了MyBatis增删改查基本操作&#xff0c;下面我们来深入了解M…

谷歌语音助手战略调整:开发 AI 新版,调整裁员计划

北京时间8月2日晚间&#xff0c;谷歌通过对 “谷歌助手” 团队进行调整和裁员&#xff0c;意图改变其开发方向。经过此次变动&#xff0c;谷歌计划借助最新的生成式人工智能技术和大型语言模型来提升 谷歌助手 的能力。此次调整表明语音助手市场未达到先前的预期。 亚马逊旗下的…

PT Industrial Security Incident Manager 扩展了对 Emerson 和 GE Fanuc 控制器的支持

&#x1f504; 我们发布了 PT Industrial Security Incident Manager (PT ISIM) 深度流程流量分析系统的下一套专业知识。 有哪些新功能&#xff1f; 1. 改进了 GE Fanuc (Emerson) GE-SRTP 协议的操作。该协议专为 GE Fanuc 控制器与工程软件&#xff08;Proficy Machine E…

ArmSoM-W3之RK3588安装Qt+opencv+采集摄像头画面

1. 简介 场景&#xff1a;在RK3588上做qt开发工作 RK3588安装Qtopencv采集摄像头画面 2. 环境介绍 这里使用了OpenCV所带的库函数捕获摄像头的视频图像。 硬件环境&#xff1a; ArmSoM-RK3588开发板、&#xff08;MIPI-DSI&#xff09;摄像头 软件版本&#xff1a; OS&…

angular框架——node_modules引入ng-zorro-antd问题

发生错误 在安装npm包&#xff08;ng-zorro-antd&#xff09;的时候&#xff0c;报错如下 原因 项目依赖的ng-zorro-antd版本号和引用包的17.0.2版本号不一致&#xff0c;项目使用的16.13.1版本号太高了&#xff0c;无法解析依赖树中低版本。 解决方案 &#xff1a; 命令修…

pointpillars的demo过程记录

1、进入pp conda activate pp 2、cd到/home/fyy/OpenPCDet-master/tools打开终端 python demo.py --cfg_file cfgs/kitti_models/pointpillar.yaml --ckpt /home/fyy/OpenPCDet-master/pointpillar_7728\ \(1\).pth --data 000009.bin 直接就可以demo显示了

数据库同步时,通过存储过程找出时间内发生变化的视图和物化视图

1. 应用场景 在软件开发或数据库运维过程&#xff0c;每一次数据库同步都是运维人员的痛苦的过程。 如果每次都是无脑全量更新&#xff0c;则工作量较大如果每次都是增量更新&#xff0c;则需要知道哪些视图谁在什么时候修改了&#xff0c;增加了什么信息&#xff08;因为往往…