SpringBoot 中使用布隆过滤器 Guava、Redission实现 1

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: SpringBoot 中使用布隆过滤器 Guava、Redission实现

昨天写了一篇Redis布隆过滤器相关的命令的文章,今天来说一说springboot中如何简单在代码中使用布隆过滤器吧。

目前市面上也有好几种实现方式,如果你需要高度定制化,可以完全从零实现,当然这不是一个简单的工程。

如果只是想快速开始的话,那么市面上现成的实现,无疑是最快的。

前言

今天说到的实现方式有以下几种:

  • 引入 Guava 实现
  • 引入 hutool 实现
  • 引入 Redission 实现
  • Guava 布隆过滤器结合 Redis (重点)

项目工程的搭建,就在这里先写明啦~

boot项目就是四步走~ 导包->写配置->编写配置类->使用

补充说明:我使用的 redis 是用docker下载的一个集成redis和布隆过滤器的镜像。安装方式:Docker安装Redis布隆过滤器

如果你是在windows上安装的redis 是3.0版本的,是无法集成布隆过滤器。

如果是在liunx版本上的redis,需要再额外下载一个布隆过滤器的模块。需要自行百度啦~


我将要用到的所有jar都放在这里啦~

 <parent>
     <artifactId>spring-boot-dependencies</artifactId>
     <groupId>org.springframework.boot</groupId>
     <version>2.5.2</version>
 </parent>
 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
     <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
     <dependency>
         <groupId>org.redisson</groupId>
         <artifactId>redisson-spring-boot-starter</artifactId>
         <version>3.17.6</version>
     </dependency>
 ​
     <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
         <version>30.0-jre</version>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
     </dependency>
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
     </dependency>
     <dependency>
         <groupId>cn.hutool</groupId>
         <artifactId>hutool-all</artifactId>
         <version>5.7.22</version>
     </dependency>
 </dependencies>
 ​

yml 配置文件:

 server:
   port: 8081
 spring:
   redis:
     port: 6379
     host: 192.xxx

一、Guava 实现布隆过滤器

这个方式非常快捷:

直接用一个Demo来说明吧

     @Test
     public void test2() {
         // 预期插入数量
         long capacity = 10000L;
         // 错误比率
         double errorRate = 0.01;
         //创建BloomFilter对象,需要传入Funnel对象,预估的元素个数,错误率
         BloomFilter<Long> filter = BloomFilter.create(Funnels.longFunnel(), capacity, errorRate);
 //        BloomFilter<String> filter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 10000, 0.0001);
         //put值进去
         for (long i = 0; i < capacity; i++) {
             filter.put(i);
         }
         // 统计误判次数
         int count = 0;
         // 我在数据范围之外的数据,测试相同量的数据,判断错误率是不是符合我们当时设定的错误率
         for (long i = capacity; i < capacity * 2; i++) {
             if (filter.mightContain(i)) {
                 count++;
             }
         }
         System.out.println(count);
     }
 ​

当容量为1k,误判率为 0.01时

 2022-08-26 23:50:01.028  INFO 14748 --- [           main] com.nzc.test.RedisBloomFilterTest        : 存入元素为==1000
 误判个数为==>10

当容量为1w,误判率为 0.01时

 2022-08-26 23:49:23.618  INFO 21796 --- [           main] com.nzc.test.RedisBloomFilterTest        : 存入元素为==10000
 误判个数为==>87
 ​

当容量为100w,误判率为 0.01时

 2022-08-26 23:50:45.167  INFO 8964 --- [           main] com.nzc.test.RedisBloomFilterTest        : 存入元素为==1000000
 误判个数为==>9946

BloomFilter filter = BloomFilter.create(Funnels.longFunnel(), capacity, errorRate);

create方法实际上调用的方法是:

 public static <T> BloomFilter<T> create(
     Funnel<? super T> funnel, int expectedInsertions, double fpp) {
   return create(funnel, (long) expectedInsertions, fpp);
 }
  • funnel 用来对参数做转化,方便生成hash值
  • expectedInsertions 预期插入的数据量大小
  • fpp 误判率

里面具体的实现,相对我来说,数学能力有限,没法说清楚。希望大家多多包含。

二、Hutool 布隆过滤器

Hutool 工具中的布隆过滤器,内存占用太高了,并且功能相比于guava也弱了很多,个人不建议使用。

 @Test
 public void test4(){
     int capacity = 100;
     // 错误比率
     double errorRate = 0.01;
     // 初始化
     BitMapBloomFilter filter = new BitMapBloomFilter(capacity);
     for (int i = 0; i < capacity; i++) {
         filter.add(String.valueOf(i));
     }
 ​
     log.info("存入元素为=={}",capacity);
     // 统计误判次数
     int count = 0;
     // 我在数据范围之外的数据,测试相同量的数据,判断错误率是不是符合我们当时设定的错误率
     for (int i = capacity; i < capacity * 2; i++) {
         if (filter.contains(String.valueOf(i))) {
             count++;
         }
     }
     log.info("误判元素为==={}",count);
 }

三、Redission 布隆过滤器

redission的使用其实也很简单,官方也有非常好的教程。

引入jar,然后编写一个config类即可

 ​
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
 <dependency>
     <groupId>org.redisson</groupId>
     <artifactId>redisson-spring-boot-starter</artifactId>
     <version>3.17.6</version>
 </dependency>

出了注入 redissionclient,还注入了一些redis相关的东西,都是历史包裹~

 /**
  * @description:
  * @author: Yihui Wang
  * @date: 2022年08月26日 22:06
  */
 @Configuration
 @EnableCaching
 public class RedisConfig {
 ​
     @Bean
     public RedissonClient redissonClient(){
         Config config = new Config();
         config.useSingleServer().setAddress("redis://47.113.227.254:6379");
         RedissonClient redissonClient = Redisson.create(config);
         return  redissonClient;
     }
 ​
     @Bean
     public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
         RedisCacheManager rcm=RedisCacheManager.create(connectionFactory);
         return rcm;
     }
     @Bean
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
         redisTemplate.setConnectionFactory(factory);
  
         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
                 Jackson2JsonRedisSerializer(Object.class);
         ObjectMapper om = new ObjectMapper();
         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         jackson2JsonRedisSerializer.setObjectMapper(om);
         //序列化设置 ,这样计算是正常显示的数据,也能正常存储和获取
         redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
         redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
         redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
         redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  
         return redisTemplate;
     }
     @Bean
     public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
         StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
         stringRedisTemplate.setConnectionFactory(factory);
         return stringRedisTemplate;
     }
 }

我们在中间再编写一个Service,

 @Service
 public class BloomFilterService {
 ​
     @Autowired
     private RedissonClient redissonClient;
 ​
     /**
      * 创建布隆过滤器
      * @param filterName 布隆过滤器名称
      * @param capacity 预测插入数量
      * @param errorRate 误判率
      * @param <T>
      * @return
      */
     public <T> RBloomFilter<T> create(String filterName, long capacity, double errorRate) {
         RBloomFilter<T> bloomFilter = redissonClient.getBloomFilter(filterName);
         bloomFilter.tryInit(capacity, errorRate);
         return bloomFilter;
     }
 }

测试:

 package com.nzc.test;
 ​
 import com.nzc.WebApplication;
 import com.nzc.service.BloomFilterService;
 import lombok.extern.slf4j.Slf4j;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.redisson.api.RBloomFilter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 ​
 @Slf4j
 @RunWith(SpringRunner.class)
 @SpringBootTest(classes = WebApplication.class)
 public class BloomFilterTest {
 ​
     @Autowired
     private BloomFilterService bloomFilterService;
 ​
     @Test
     public void testBloomFilter() {
         // 预期插入数量
         long expectedInsertions = 1000L;
         // 错误比率
         double falseProbability = 0.01;
         RBloomFilter<Long> bloomFilter = bloomFilterService.create("NZC:BOOM-FILTER", expectedInsertions, falseProbability);
         // 布隆过滤器增加元素
         for (long i = 0; i < expectedInsertions; i++) {
             bloomFilter.add(i);
         }
         long elementCount = bloomFilter.count();
         log.info("布隆过滤器中含有元素个数 = {}.", elementCount);
 ​
         // 统计误判次数
         int count = 0;
         // 我在数据范围之外的数据,测试相同量的数据,判断错误率是不是符合我们当时设定的错误率
         for (long i = expectedInsertions; i < expectedInsertions * 2; i++) {
             if (bloomFilter.contains(i)) {
                 count++;
             }
         }
         log.info("误判次数 = {}.", count);
 ​
         // 清空布隆过滤器 内部实现是个异步线程在执行  我只是为了方便测试
         bloomFilter.delete();
     }
 }

当容量为1k,误判率为0.01时的输出情况

 2022-08-26 23:37:04.903  INFO 1472 --- [           main] com.nzc.test.BloomFilterTest             : 布隆过滤器中含有元素个数 = 993.
 2022-08-26 23:37:38.549  INFO 1472 --- [           main] com.nzc.test.BloomFilterTest             : 误判次数 = 36.

当容量为1w,误判率为0.01时的输出情况

 2022-08-26 23:50:54.478  INFO 17088 --- [           main] com.nzc.test.BloomFilterTest             : 布隆过滤器中含有元素个数 = 9895.
 2022-08-26 23:56:56.171  INFO 17088 --- [           main] com.nzc.test.BloomFilterTest             : 误判次数 = 259.

SpringBoot 中使用布隆过滤器 Guava、Redission实现2:https://developer.aliyun.com/article/1394548

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
数据采集 存储 缓存
SpringBoot与布隆过滤器的完美邂逅:高效防护大规模数据的奇妙结合【实战】
SpringBoot与布隆过滤器的完美邂逅:高效防护大规模数据的奇妙结合【实战】
148 0
|
存储 缓存 NoSQL
(Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
(Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
1103 0
(Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
|
2月前
|
缓存 NoSQL Java
【二十六】springboot整合jedis和redisson布隆过滤器处理缓存穿透
【二十六】springboot整合jedis和redisson布隆过滤器处理缓存穿透
134 0
|
2月前
|
缓存 NoSQL Java
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
139 0
|
2月前
|
NoSQL Java Redis
SpringBoot 中使用布隆过滤器 Guava、Redission实现2
SpringBoot 中使用布隆过滤器 Guava、Redission实现
74 0
|
10月前
|
缓存 NoSQL Java
SpringBoot 如何集成 Redis 实现布隆过滤器
针对 Redis 的缓存穿透问题,布隆过滤器是一个常见的解决办法。在单机的场景下,我们可以使用谷歌的 guava 包里面提供的布隆过滤器。在分布式的场景下,我们也可以选用 Redis 来实现布隆过滤器。
233 0
|
10月前
|
人工智能 NoSQL Java
springboot 高级教程 如何优雅使用redission分布式锁
springboot 高级教程 如何优雅使用redission分布式锁
757 0
|
11月前
|
存储 NoSQL Java
基于springboot+Redis的前后端分离项目之分布式锁-redission(五)-【黑马点评】
基于setnx实现的分布式锁存在下面的问题: 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
springBoot整合redission框架
springBoot整合redission框架
|
Java Spring
Springboot 使用 Guava 的重试Retry ,轻便灵活
Springboot 使用 Guava 的重试Retry ,轻便灵活
535 0
Springboot 使用 Guava 的重试Retry ,轻便灵活