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

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

在这里插入图片描述

文章目录

  • 问题背景
  • 前言
  • 布隆过滤器原理
  • 使用场景
  • 基础中间件搭建
  • 如何实现布隆过滤器
    • 引入依赖
    • 注入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*/@Beanpublic 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和publicom.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*/@Beanpublic HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForHash();}/*** 对redis字符串类型数据操作** @param redisTemplate* @return*/@Beanpublic ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForValue();}/*** 对链表类型的数据操作** @param redisTemplate* @return*/@Beanpublic ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForList();}/*** 对无序集合类型的数据操作** @param redisTemplate* @return*/@Beanpublic SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {return redisTemplate.opsForSet();}/*** 对有序集合类型的数据操作** @param redisTemplate* @return*/@Beanpublic 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 {@Autowiredprivate 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。查询一次仅需几十毫秒。
  • 优点:空间效率高;;查询时间快;支持高并发。
  • 缺点:存在一定的误判率;不支持元素的删除,如需删除,需要重新构建布隆过滤器。
  • 使用场景:在生产环境中,可以先把数据加载进布隆过滤器,然后用来做防穿透或者去重。

相关文章:

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

文章目录 问题背景前言布隆过滤器原理使用场景基础中间件搭建如何实现布隆过滤器引入依赖注入RedisTemplate布隆过滤器核心代码Redis操作布隆过滤器验证 总结 问题背景 研究布隆过滤器的实现方式以及使用场景 前言 本篇的代码都是参考SpringBootRedis布隆过滤器防恶意流量击穿缓…...

SpringBoot第25讲:SpringBoot集成MySQL - MyBatis 注解方式

SpringBoot第25讲&#xff1a;SpringBoot集成MySQL - MyBatis 注解方式 本文是SpringBoot第25讲&#xff0c;上文主要介绍了Spring集成MyBatis访问MySQL&#xff0c;采用的是XML配置方式&#xff1b;我们知道除了XML配置方式&#xff0c;MyBatis还支持注解方式。本文主要介绍Sp…...

服务器返回 413 Request Entity Too Large

问题 上传一个大于1.5M的文件时&#xff0c;报错&#xff1a;413 Request Entity Too Large 使用的配置 1、用的是docker环境&#xff0c;还有一层代理&#xff0c;代理用的镜像是&#xff1a;jwilder/nginx-proxy 2、docker里是有php和nginx 确认配置 docker里的php和ngi…...

如何一目了然地监控远程 Linux 系统

动动发财的小手&#xff0c;点个赞吧&#xff01; Glances 是一款免费的开源、现代、跨平台、实时 top 和类似 htop 的系统监控工具&#xff0c;与同类工具相比&#xff0c;它提供了先进的功能&#xff0c;并且可以在不同的模式下运行&#xff1a;作为独立模式、客户端/服务器模…...

9.环境对象和回调函数

9.1环境对象 指的是函数内部特殊的变量this&#xff0c;它代表着当前函数运行时所处的环境 作用&#xff1a; 弄清楚this的指向&#xff0c;可以让我们代码更简洁 ➢函数的调用方式不同&#xff0c;this指代的对象也不同 ➢[谁调用&#xff0c;this 就指代谁] 是判断this指向的…...

51单片机(普中HC6800-EM3 V3.0)实验例程软件分析概览

本专栏将分析普中HC6800-EM3 V3.0 (9.22)\5--实验程序\基础实验例程中的各个例程的代码。 引言:本专栏将对历程中的关键代码进行分析与拓展,再学习一遍51,记录与各位一起进步。 下面是文件列表: E:\USER\000study\000_51单片机\000普中HC6800-EM3 V3.0 (9.22)\5--实…...

ubuntu18.04 安装php7.4-xdebug

文章目录 场景解决 场景 apt install php7.4-xdebug 下载失败, 只好通过编译解决了 解决 https://xdebug.org/wizard 输入php -i的执行结果...

java 定时任务不按照规定时间执行

这里写目录标题 使用异步启动可能出现的问题排查代码中添加的定时任务步骤是否正确排查是否任务阻塞&#xff0c;如果定时任务出现异常阻塞后&#xff0c;将不会在次执行java中多个Scheduled定时器不执行为了让Scheduled效率更高&#xff0c;我们可以通过两种方法将定时任务变成…...

Android复习(Android基础-四大组件)—— Activity

Activity作为四大组件之首&#xff0c;是使用最为频繁的一种组件&#xff0c;中文直接翻译为"活动"&#xff0c;不过如果被翻译为"界面"会更好理解。正常情况&#xff0c;除了Window&#xff0c;Dialog和Toast &#xff0c; 我们能见到的界面只有Activity。…...

Linux系统安装部署MongoDB完整教程(图文详解)

前言&#xff1a;本期给大家分享一下目前最新Linux系统安装部署MongoDB完整教程&#xff0c;我的服务器采用的是Centos7&#xff0c;在部署之前我重装了我的服务器&#xff0c;目的是为了干净整洁的给大家演示我是如何一步步的操作的&#xff0c;整体部署还是挺简洁&#xff0c…...

CSS图片放到<div>里面,自适应宽高全部显示,点击图片跳到新页面预览,点击旋转按钮图片可旋转

有一个需求是图片放到一个固定宽高的<div>里面&#xff0c;不管是横图还是竖图&#xff0c;都要全部显示出来并且保持图片的长宽比例不变形&#xff0c;点击图片可以跳到一个新页面预览&#xff0c;代码如下&#xff1a; <!DOCTYPE html> <html> <head>…...

二阶段web基础与http协议

dns与域名 网络是基于tcp/ip协议进行通信和连接的 应用层-----传输层-----网络层-----数据链路层-----物理层 ip地址&#xff0c;每一台主机都有一个唯一的地址标识&#xff08;固定的ip地址&#xff09; 1.区分用户和计算机 2.通信 ip地址的问题在于32位二进制数组成的&…...

SpringBoot+Freemark根据html模板动态导出PDF

SpringBootFreemark根据html模板导出PDF 1、引入maven2、两个工具类2.1 test.html模板2.2 test.html模板中的Freemark语法 3、controller导出pdf 1、引入maven 导出pdf的一些必要jar包 <dependency><groupId>org.projectlombok</groupId><artifactId>…...

XPath数据提取与贴吧爬虫应用示例

XPath数据提取与贴吧爬虫应用示例 XpathXpath概述Xpath Helper插件 XPath语法基本语法查找特定节点选取未知节点选取若干路径 lxml模块使用说明使用示例 百度贴吧爬虫 Xpath Xpath概述 XPath&#xff08;XML Path Language&#xff09;是一种用于在XML文档中定位和选择节点的语…...

字符串匹配-KMP算法

KMP算法&#xff0c;字符串匹配算法&#xff0c;给定一个主串S&#xff0c;和一个字串T,返回字串T与之S匹配的数组下标。 在学KMP算法之前&#xff0c;对于两个字符串&#xff0c;主串S&#xff0c;和字串T&#xff0c;我们根据暴力匹配&#xff0c;定义两个指针&#xff0c;i指…...

Java面向对象之UML类图

UML类图 表示 public 类型&#xff0c; - 表示 private 类型&#xff0c;#表示protected类型方法的写法&#xff1a;方法的类型(、-) 方法名(参数名&#xff1a; 参数类型)&#xff1a;返回值类型...

【机器学习】西瓜书学习心得及课后习题参考答案—第4章决策树

这一章学起来较为简单&#xff0c;也比较好理解。 4.1基本流程——介绍了决策树的一个基本的流程。叶结点对应于决策结果&#xff0c;其他每个结点则对应于一个属性测试&#xff1b;每个结点包含的样本集合根据属性测试的结果被划分到子结点中&#xff1b;根结点包含样本全集&a…...

2023.8.2

2022河南萌新联赛第&#xff08;三&#xff09;场&#xff1a;河南大学\神奇数字.cpp //题意&#xff1a;给定三个正整数a b c,求x满足满足abc同余x的个数。 //这个考虑同余的性质&#xff0c;就是两个数的差去取模为0的数肯定是这两个数的同余数,。因此我们计算三个数两两之…...

windows运行窗口常用快捷键命令

winr打开运行窗口,然后输入快捷命令:&#xff08;当然utools和win11搜索也挺好用的&#xff09; cmd : 命令行窗口&#xff08;命令提示符窗口、cmd窗口&#xff09;regedit : 注册表mspaint : 画图工具services.msc : 本地服务设置(比如查看mysql服务是否启动成功)devmgmt.ms…...

HDFS的QJM方案

Quorum Journal Manager仲裁日志管理器 介绍主备切换&#xff0c;脑裂问题解决---ZKFailoverController&#xff08;zkfc&#xff09;主备切换&#xff0c;脑裂问题解决-- Fencing&#xff08;隔离&#xff09;机制主备数据状态同步问题解决 HA集群搭建集群基础环境准备HA集群规…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...