Redis布隆过滤器的保姆级教程

以「生产环境首选的 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 项目

  1. 打开 IDEA → 新建项目 → 选择「Spring Initializr」→ 填写项目信息(Group/Artifact);
  2. 选择依赖:Spring Web + Spring Data Redis(核心依赖);
  3. 点击「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 执行测试步骤(新手必看)

  1. 确保 Redis 容器处于运行状态(docker ps 查看);
  2. 右键点击测试类 → 选择「Run 'SpringbootBloomfilterApplicationTests'」;
  3. 按顺序执行 testInitBloomFiltertestAddElementtestBatchAddtestExists
  4. 查看控制台输出,验证所有功能是否正常。

五、备用方案:手动基于 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
}

六、常见问题解决(保姆级避坑)

  1. 报错「ERR unknown command 'BF.RESERVE'」

    → 原因:Redis 未安装 RedisBloom 模块;

    → 解决:重新用 Docker 启动包含 RedisBloom 的容器,或切换到手动 Bitmap 方案。

  2. Redis 连接超时/拒绝连接

    → 检查 Redis 容器是否运行(docker start redis-bloom);

    → 检查 application.yml 中的 host/port/password 是否和容器配置一致。

  3. 误判率过高

    → RedisBloom 方案:初始化时减小 errorRate(如 0.001)、增大 capacity

    → 手动方案:增大 BIT_SIZEHASH_NUM


总结

  1. 生产首选 :SpringBoot + RedisBloom 模块,只需封装工具类调用 BF.RESERVE/ADD/EXISTS 命令,高效且易维护;
  2. 核心步骤:部署带 RedisBloom 的 Redis → 引入 Spring Redis 依赖 → 配置 RedisTemplate → 封装工具类 → 测试验证;
  3. 备用方案:无 RedisBloom 时,手动基于 Bitmap 实现,核心是「多哈希函数映射到位图位」,需手动调参控制误判率。

这份教程的代码可直接复制到你的 SpringBoot 项目中,只需修改 Redis 连接信息即可使用,新手也能快速落地。

相关推荐
佛祖让我来巡山7 天前
Redis快速实现布隆过滤器:缓存去重的“智能门卫”
布隆过滤器
爱敲代码的TOM16 天前
详解布隆过滤器及其实战案例
redis·布隆过滤器
蜂蜜黄油呀土豆1 个月前
Redis 高并发场景与数据一致性问题深度解析
redis·分布式锁·秒杀系统·数据一致性·布隆过滤器
佛祖让我来巡山3 个月前
布谷鸟过滤器详解:从原理到Spring Boot实战
布隆过滤器·布谷鸟过滤器
佛祖让我来巡山3 个月前
布隆过滤器的完整最佳实践案例
布隆过滤器
JanelSirry3 个月前
真实场景:防止缓存穿透 —— 使用 Redisson 布隆过滤器
数据库·mysql·缓存·redisson·布隆过滤器
xiucai_cs6 个月前
布隆过滤器原理与Spring Boot实战
java·spring boot·后端·布隆过滤器
陈振wx:zchen20086 个月前
7、如何管理昵称重复?
布隆过滤器·场景题-判重
失散136 个月前
大型微服务项目:听书——11 Redisson分布式布隆过滤器+Redisson分布式锁改造专辑详情接口
分布式·缓存·微服务·架构·布隆过滤器