以「生产环境首选的 RedisBloom 模块」为核心,兼顾「无模块时的手动 Bitmap 实现方案」,全程步骤拆解到最小单元,新手也能跟着做。
一、前置环境准备
1.1 确保 Redis 已安装 RedisBloom 模块
布隆过滤器的高效实现依赖 RedisBloom 扩展,优先用 Docker 快速部署(新手友好):- # 1. 拉取包含 RedisBloom 的镜像(国内可加镜像源加速)
- docker pull redislabs/rebloom:latest
- # 2. 启动 Redis 容器(映射端口 6379,设置密码 123456,方便后续配置)
- docker run -d --name redis-bloom -p 6379:6379 -e REDIS_PASSWORD=123456 redislabs/rebloom:latest
复制代码 验证 RedisBloom 是否安装成功:- # 进入容器
- docker exec -it redis-bloom redis-cli
- # 输入密码(如果设置了)
- 127.0.0.1:6379> AUTH 123456
- OK
- # 执行 BF.RESERVE 命令,返回 OK 则说明模块正常
- 127.0.0.1:6379> BF.RESERVE test_bloom 0.01 10000
- OK
复制代码 二、SpringBoot 项目搭建(IDEA 为例)
2.1 创建基础 SpringBoot 项目
- 打开 IDEA → 新建项目 → 选择「Spring Initializr」→ 填写项目信息(Group/Artifact);
- 选择依赖:Spring Web + Spring Data Redis(核心依赖);
- 点击「Finish」完成创建。
2.2 引入核心依赖(pom.xml)
确保 pom.xml 包含以下依赖(版本可根据 SpringBoot 版本适配):- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-parent</artifactId>
- <version>2.7.15</version>
- <relativePath/>
- </parent>
-
- <groupId>com.example</groupId>
- springboot-bloomfilter</artifactId>
- <version>0.0.1-SNAPSHOT</version>
-
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-web</artifactId>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-data-redis</artifactId>
- </dependency>
-
-
- <dependency>
- <groupId>org.apache.commons</groupId>
- commons-pool2</artifactId>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
- </project>
复制代码 2.3 配置 Redis 连接(application.yml)
在 src/main/resources 下创建/修改 application.yml,配置 Redis 连接信息(和前面启动的容器对应):- spring:
- # Redis 配置
- redis:
- host: localhost # Docker 部署的 Redis 地址,本地填 localhost
- port: 6379 # 映射的端口
- password: 123456 # 启动容器时设置的密码
- database: 0 # 使用第 0 个数据库
- # 连接池配置(关键,避免频繁创建连接)
- lettuce:
- pool:
- max-active: 8 # 最大连接数
- max-idle: 8 # 最大空闲连接
- min-idle: 0 # 最小空闲连接
- max-wait: 1000ms # 连接等待时间
复制代码 三、核心代码编写(RedisBloom 方案)
3.1 配置 RedisTemplate(解决序列化问题)
创建 config/RedisConfig.java,配置 RedisTemplate 确保命令执行正常:- package com.example.springbootbloomfilter.config;
- 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.RedisTemplate;
- import org.springframework.data.redis.serializer.StringRedisSerializer;
- /**
- * Redis 配置类:解决序列化问题,确保 RedisTemplate 能正常执行 BF 命令
- */
- @Configuration
- public class RedisConfig {
- @Bean
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- redisTemplate.setConnectionFactory(factory);
- // 设置 key 和 value 的序列化器(String 序列化,避免乱码)
- StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
- redisTemplate.setKeySerializer(stringRedisSerializer);
- redisTemplate.setValueSerializer(stringRedisSerializer);
- redisTemplate.setHashKeySerializer(stringRedisSerializer);
- redisTemplate.setHashValueSerializer(stringRedisSerializer);
- redisTemplate.afterPropertiesSet();
- return redisTemplate;
- }
- }
复制代码 3.2 封装布隆过滤器工具类(核心)
创建 util/RedisBloomFilterUtil.java,封装布隆过滤器的核心操作(初始化、添加、查询),新手可直接复制使用:- package com.example.springbootbloomfilter.util;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Component;
- import javax.annotation.Resource;
- /**
- * Redis 布隆过滤器工具类(基于 RedisBloom 模块)
- * 保姆级封装:所有方法直接调用,无需关心底层细节
- */
- @Component
- public class RedisBloomFilterUtil {
- // 注入配置好的 RedisTemplate
- @Resource
- private RedisTemplate<String, Object> redisTemplate;
- /**
- * 初始化布隆过滤器
- * @param bloomKey 布隆过滤器的 key
- * @param errorRate 误判率(推荐 0.01~0.001)
- * @param capacity 预计存储的元素数量
- * @return true=初始化成功,false=已存在(无需重复初始化)
- */
- public boolean initBloomFilter(String bloomKey, double errorRate, long capacity) {
- try {
- // 执行 BF.RESERVE 命令初始化
- redisTemplate.execute((connection) -> {
- connection.execute("BF.RESERVE",
- bloomKey.getBytes(),
- String.valueOf(errorRate).getBytes(),
- String.valueOf(capacity).getBytes());
- return null;
- });
- System.out.println("布隆过滤器 [" + bloomKey + "] 初始化成功");
- return true;
- } catch (Exception e) {
- // 捕获“已存在”异常,避免重复初始化报错
- if (e.getMessage().contains("already exists")) {
- System.out.println("布隆过滤器 [" + bloomKey + "] 已存在,无需重复初始化");
- return false;
- }
- // 其他异常抛出,方便排查问题
- throw new RuntimeException("初始化布隆过滤器失败:" + e.getMessage(), e);
- }
- }
- /**
- * 添加单个元素到布隆过滤器
- * @param bloomKey 布隆过滤器的 key
- * @param element 要添加的元素(如 URL、用户ID)
- * @return true=添加成功,false=元素已存在(RedisBloom 模块特性)
- */
- public boolean add(String bloomKey, String element) {
- try {
- Long result = (Long) redisTemplate.execute((connection) -> {
- return connection.execute("BF.ADD",
- bloomKey.getBytes(),
- element.getBytes());
- });
- return result != null && result == 1;
- } catch (Exception e) {
- throw new RuntimeException("添加元素到布隆过滤器失败:" + e.getMessage(), e);
- }
- }
- /**
- * 批量添加元素到布隆过滤器
- * @param bloomKey 布隆过滤器的 key
- * @param elements 要添加的元素数组
- * @return 成功添加的元素数量
- */
- public long batchAdd(String bloomKey, String[] elements) {
- try {
- // 转换参数为字节数组
- byte[][] args = new byte[elements.length + 1][];
- args[0] = bloomKey.getBytes();
- for (int i = 0; i < elements.length; i++) {
- args[i + 1] = elements[i].getBytes();
- }
- Long[] results = (Long[]) redisTemplate.execute((connection) -> {
- return connection.execute("BF.MADD", args);
- });
- // 统计成功添加的数量(返回 1 表示添加成功,0 表示已存在)
- long count = 0;
- if (results != null) {
- for (Long result : results) {
- if (result == 1) {
- count++;
- }
- }
- }
- System.out.println("批量添加 " + count + " 个元素到布隆过滤器 [" + bloomKey + "]");
- return count;
- } catch (Exception e) {
- throw new RuntimeException("批量添加元素失败:" + e.getMessage(), e);
- }
- }
- /**
- * 判断元素是否存在于布隆过滤器中
- * @param bloomKey 布隆过滤器的 key
- * @param element 要查询的元素
- * @return true=可能存在(有误判率),false=绝对不存在
- */
- public boolean exists(String bloomKey, String element) {
- try {
- Long result = (Long) redisTemplate.execute((connection) -> {
- return connection.execute("BF.EXISTS",
- bloomKey.getBytes(),
- element.getBytes());
- });
- return result != null && result == 1;
- } catch (Exception e) {
- throw new RuntimeException("查询布隆过滤器失败:" + e.getMessage(), e);
- }
- }
- }
复制代码 四、测试验证(保姆级测试步骤)
创建 test/java/com/example/springbootbloomfilter/SpringbootBloomfilterApplicationTests.java,编写单元测试验证所有功能:- package com.example.springbootbloomfilter;
- import com.example.springbootbloomfilter.util.RedisBloomFilterUtil;
- import org.junit.jupiter.api.Test;
- import org.springframework.boot.test.context.SpringBootTest;
- import javax.annotation.Resource;
- /**
- * 布隆过滤器测试类:逐一验证初始化、添加、查询功能
- */
- @SpringBootTest
- class SpringbootBloomfilterApplicationTests {
- // 注入封装好的工具类
- @Resource
- private RedisBloomFilterUtil redisBloomFilterUtil;
- // 定义布隆过滤器的 key(统一管理,避免写错)
- private static final String BLOOM_KEY = "user_id_bloom";
- @Test
- void testInitBloomFilter() {
- // 初始化:误判率 0.01(1%),预计存储 10000 个用户ID
- redisBloomFilterUtil.initBloomFilter(BLOOM_KEY, 0.01, 10000);
- }
- @Test
- void testAddElement() {
- // 添加单个元素:用户ID 10001
- boolean addResult = redisBloomFilterUtil.add(BLOOM_KEY, "10001");
- System.out.println("添加用户ID 10001 结果:" + addResult); // true
- // 重复添加同一个元素
- boolean addResult2 = redisBloomFilterUtil.add(BLOOM_KEY, "10001");
- System.out.println("重复添加用户ID 10001 结果:" + addResult2); // false(已存在)
- }
- @Test
- void testBatchAdd() {
- // 批量添加用户ID:10002、10003、10004
- String[] userIds = {"10002", "10003", "10004"};
- long count = redisBloomFilterUtil.batchAdd(BLOOM_KEY, userIds);
- System.out.println("批量添加成功数量:" + count); // 3
- }
- @Test
- void testExists() {
- // 查询存在的元素:10001
- boolean exists1 = redisBloomFilterUtil.exists(BLOOM_KEY, "10001");
- System.out.println("用户ID 10001 是否存在:" + exists1); // true
- // 查询不存在的元素:99999
- boolean exists2 = redisBloomFilterUtil.exists(BLOOM_KEY, "99999");
- System.out.println("用户ID 99999 是否存在:" + exists2); // false
- // 查询批量添加的元素:10003
- boolean exists3 = redisBloomFilterUtil.exists(BLOOM_KEY, "10003");
- System.out.println("用户ID 10003 是否存在:" + exists3); // true
- }
- }
复制代码 4.1 执行测试步骤(新手必看)
- 确保 Redis 容器处于运行状态(docker ps 查看);
- 右键点击测试类 → 选择「Run 'SpringbootBloomfilterApplicationTests'」;
- 按顺序执行 testInitBloomFilter → testAddElement → testBatchAdd → testExists;
- 查看控制台输出,验证所有功能是否正常。
五、备用方案:手动基于 Bitmap 实现(无 RedisBloom 模块)
如果你的 Redis 无法安装 RedisBloom 模块,可手动基于 Bitmap 实现,核心是「多哈希函数映射到位图位」:
5.1 手动布隆过滤器工具类
- package com.example.springbootbloomfilter.util;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Component;
- import javax.annotation.Resource;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.util.Arrays;
- import java.util.List;
- /**
- * 手动基于 Bitmap 实现的布隆过滤器(无 RedisBloom 模块时使用)
- */
- @Component
- public class ManualBloomFilterUtil {
- @Resource
- private RedisTemplate<String, Object> redisTemplate;
- // 位图总位数(越大误判率越低,1000000 位 ≈ 1.2MB 内存)
- private static final int BIT_SIZE = 1000000;
- // 哈希函数个数(越多误判率越低,推荐 6~8 个)
- private static final int HASH_NUM = 6;
- /**
- * 生成元素对应的多个哈希索引
- */
- private List<Integer> getHashIndexes(String element) {
- int[] indexes = new int[HASH_NUM];
- try {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- byte[] digest = md5.digest(element.getBytes());
- // 从 MD5 摘要中生成多个哈希值
- for (int i = 0; i < HASH_NUM; i++) {
- int idx = (digest[i] & 0xFF) % BIT_SIZE;
- indexes[i] = Math.abs(idx); // 避免负数索引
- }
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("哈希算法异常", e);
- }
- return Arrays.asList(Arrays.stream(indexes).boxed().toArray(Integer[]::new));
- }
- /**
- * 添加元素到布隆过滤器
- */
- public void add(String bloomKey, String element) {
- List<Integer> indexes = getHashIndexes(element);
- for (Integer idx : indexes) {
- // 设置位图对应位为 1
- redisTemplate.opsForValue().setBit(bloomKey, idx, true);
- }
- }
- /**
- * 判断元素是否存在
- */
- public boolean exists(String bloomKey, String element) {
- List<Integer> indexes = getHashIndexes(element);
- for (Integer idx : indexes) {
- // 只要有一个位为 0,说明绝对不存在
- if (!redisTemplate.opsForValue().getBit(bloomKey, idx)) {
- return false;
- }
- }
- // 所有位都为 1,可能存在(有误差)
- return true;
- }
- }
复制代码 5.2 手动实现的测试方法
在测试类中添加以下方法,验证手动实现的布隆过滤器:- @Resource
- private ManualBloomFilterUtil manualBloomFilterUtil;
- @Test
- void testManualBloomFilter() {
- String manualBloomKey = "manual_user_bloom";
-
- // 添加元素
- manualBloomFilterUtil.add(manualBloomKey, "20001");
- manualBloomFilterUtil.add(manualBloomKey, "20002");
-
- // 查询元素
- boolean exists1 = manualBloomFilterUtil.exists(manualBloomKey, "20001");
- boolean exists2 = manualBloomFilterUtil.exists(manualBloomKey, "99999");
- System.out.println("手动实现-20001 是否存在:" + exists1); // true
- System.out.println("手动实现-99999 是否存在:" + exists2); // false
- }
复制代码 六、常见问题解决(保姆级避坑)
- 报错「ERR unknown command 'BF.RESERVE'」:
→ 原因:Redis 未安装 RedisBloom 模块;
→ 解决:重新用 Docker 启动包含 RedisBloom 的容器,或切换到手动 Bitmap 方案。
- Redis 连接超时/拒绝连接:
→ 检查 Redis 容器是否运行(docker start redis-bloom);
→ 检查 application.yml 中的 host/port/password 是否和容器配置一致。
- 误判率过高:
→ RedisBloom 方案:初始化时减小 errorRate(如 0.001)、增大 capacity;
→ 手动方案:增大 BIT_SIZE 或 HASH_NUM。
总结
- 生产首选:SpringBoot + RedisBloom 模块,只需封装工具类调用 BF.RESERVE/ADD/EXISTS 命令,高效且易维护;
- 核心步骤:部署带 RedisBloom 的 Redis → 引入 Spring Redis 依赖 → 配置 RedisTemplate → 封装工具类 → 测试验证;
- 备用方案:无 RedisBloom 时,手动基于 Bitmap 实现,核心是「多哈希函数映射到位图位」,需手动调参控制误判率。
这份教程的代码可直接复制到你的 SpringBoot 项目中,只需修改 Redis 连接信息即可使用,新手也能快速落地。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |