以「生产环境首选的 RedisBloom 模块」为核心,兼顾「无模块时的手动 Bitmap 实现方案」,全程步骤拆解到最小单元,新手也能跟着做。
一、前置环境准备
1.1 确保 Redis 已安装 RedisBloom 模块
布隆过滤器的高效实现依赖 RedisBloom 扩展,优先用 Docker 快速部署(新手友好):
bash
# 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 是否安装成功:
bash
# 进入容器
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
<?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>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version> <!-- 稳定版本,新手推荐 -->
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-bloomfilter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Spring Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis 核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis 连接池(必须,否则报连接异常) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.3 配置 Redis 连接(application.yml)
在 src/main/resources 下创建/修改 application.yml,配置 Redis 连接信息(和前面启动的容器对应):
yaml
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 确保命令执行正常:
java
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,封装布隆过滤器的核心操作(初始化、添加、查询),新手可直接复制使用:
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,编写单元测试验证所有功能:
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 手动布隆过滤器工具类
java
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 手动实现的测试方法
在测试类中添加以下方法,验证手动实现的布隆过滤器:
java
@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 连接信息即可使用,新手也能快速落地。