【算法】【搜索算法】【实战】----基于redisson实现分布式布隆过滤器实战案例并测试

前言:

布隆过滤可以解决最经典的缓存击穿的问题。可以防止恶意请求不存在的key,导致大量恶意请求打到mysql数据库。

布隆过滤器的核心特性是:

不会漏判(如果元素实际存在,一定会判断为存在)
可能误判(如果元素实际不存在,可能会判断为存在)

在这里插入图片描述

一、技术选型:

在Java中,布隆过滤器的第三方实现有多种,常见的包括以下几种:

  1. Google Guava:Guava库中提供了BloomFilter实现,这是最常用的布隆过滤器之一。它支持自定义预期插入数量、误判率等参数,使用简单且性能稳定。
  2. Apache Commons Commons Collections Collections:该库中也有布隆过滤器的实现,不过相比Guava,其功能和灵活性稍逊一筹。
  3. Redisson:如果你在使用Redis,Redisson提供了分布式环境下的布隆过滤器实现,适合分布式场景。
  4. Hutool:国产的Java工具类库Hutool也包含了布隆过滤器的实现,API设计简洁,易于上手。
  5. Tair:阿里开源的分布式存储框架Tair中也有布隆过滤器的实现,适用于特定的分布式存储场景。
    结论:
    单机 模式 :推荐使用谷歌的 guava 。
    分布式模式:推荐使用 redisson,它能在集群环境下保持数据一致性。

二、实战案例

先预热10万个key,用100个key做测试并输出准确率

引入依赖

<!-- Redisson 核心依赖 -->
<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson-spring-boot-starter</artifactId>
	<version>3.23.5</version>
</dependency>

redis配置类 RedissonConfig

package test.bloomfilter;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Redisson配置类
 * 配置Redisson客户端连接Redis服务器
 *
 * @author 架构师
 * @date 2025-09-11
 */
@Configuration
public class RedissonConfig {

    /**
     * 创建Redisson客户端实例
     * 此处使用单节点Redis配置,实际生产环境可根据需求修改为集群或哨兵模式
     * @return RedissonClient实例
     */
    @Bean
    public RedissonClient redissonClient() {
        // 创建配置对象
        Config config = new Config();
        // 配置单节点Redis,实际使用时替换为真实的Redis地址
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setDatabase(0)
                .setConnectionPoolSize(16)
                .setConnectionMinimumIdleSize(8);
        // 根据配置创建Redisson客户端实例
        return Redisson.create(config);
    }
}

业务接口类 RedissonBloomFilterService

package test.bloomfilter;

import java.util.Collection;

/**
 * 布隆过滤器服务接口
 * 提供添加元素、批量添加和判断元素是否存在的功能
 *
 * @author 架构师
 * @date 2025-09-11
 */
public interface RedissonBloomFilterService {

    /**
     * 初始化布隆过滤器
     *
     * @param expectedInsertions 预期插入的元素数量
     * @param falseProbability   可接受的误判概率
     */
    void init(long expectedInsertions, double falseProbability);

    /**
     * 向布隆过滤器中添加单个元素
     *
     * @param element 要添加的元素
     * @return 是否添加成功
     */
    boolean add(String element);

    /**
     * 向布隆过滤器中批量添加元素
     *
     * @param elements 要添加的元素集合
     * @return 是否全部添加成功
     */
    boolean addAll(Collection<String> elements);

    /**
     * 判断元素是否可能存在于布隆过滤器中
     *
     * @param element 要判断的元素
     * @return true: 可能存在, false: 一定不存在
     */
    boolean contains(String element);
}

接口实现

package test.bloomfilter;

import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.Collection;

/**
 * Redisson布隆过滤器服务实现类
 * 基于Redisson客户端实现布隆过滤器功能
 *
 * @author 架构师
 * @date 2025-09-11
 */
@Service
@RequiredArgsConstructor
public class RedissonBloomFilterServiceImpl implements RedissonBloomFilterService {

    private final RedissonClient redissonClient;
    private RBloomFilter<String> bloomFilter;
    private static final String BLOOM_FILTER_NAME = "custom_bloom_filter";

    /**
     * 初始化布隆过滤器
     *
     * @param expectedInsertions 预期插入的元素数量
     * @param falseProbability   可接受的误判概率
     */
    @Override
    public void init(long expectedInsertions, double falseProbability) {
        // 获取或创建布隆过滤器
        bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);

        // 初始化布隆过滤器参数
        // 注意:如果布隆过滤器已存在,多次调用tryInit不会改变其配置
        bloomFilter.tryInit(expectedInsertions, falseProbability);
    }

    /**
     * 向布隆过滤器中添加单个元素
     *
     * @param element 要添加的元素
     * @return 是否添加成功
     */
    @Override
    public boolean add(String element) {
        validateBloomFilterInitialized();
        return bloomFilter.add(element);
    }

    /**
     * 向布隆过滤器中批量添加元素
     *
     * @param elements 要添加的元素集合
     * @return 是否全部添加成功
     */
    @Override
    public boolean addAll(Collection<String> elements) {
        validateBloomFilterInitialized();
        boolean allAdded = true;
        for (String element : elements) {
            if (!bloomFilter.add(element)) {
                allAdded = false;
            }
        }
        return allAdded;
    }

    /**
     * 判断元素是否可能存在于布隆过滤器中
     *
     * @param element 要判断的元素
     * @return true: 可能存在, false: 一定不存在
     */
    @Override
    public boolean contains(String element) {
        validateBloomFilterInitialized();
        return bloomFilter.contains(element);
    }

    /**
     * 验证布隆过滤器是否已初始化
     * 若未初始化则抛出IllegalStateException异常
     */
    private void validateBloomFilterInitialized() {
        if (bloomFilter == null) {
            throw new IllegalStateException("布隆过滤器尚未初始化,请先调用init方法");
        }
    }
}

测试类 BloomFilterTestMain

package test.bloomfilter;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

/**
 * 布隆过滤器测试主类
 * 用于测试布隆过滤器的准确性和性能
 * @author 架构师
 * @date 2025-09-11
 */
public class BloomFilterTestMain {

    /**
     * 预热数据量
     */
    private static final int WARMUP_DATA_SIZE = 100000;

    /**
     * 测试数据量
     */
    private static final int TEST_DATA_SIZE = 100;

    /**
     * 测试数据中存在的数据比例 (0.5表示50%的数据存在于预热数据中)
     */
    private static final double EXIST_RATIO = 0.5;

    /**
     * 主方法,执行布隆过滤器测试
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 初始化Spring上下文
        ApplicationContext context = new AnnotationConfigApplicationContext(RedissonConfig.class, RedissonBloomFilterServiceImpl.class);

        // 获取布隆过滤器服务实例
        RedissonBloomFilterService bloomFilterService = context.getBean(RedissonBloomFilterServiceImpl.class);

        // 初始化布隆过滤器:预期10万个元素,误判率0.01
        bloomFilterService.init(WARMUP_DATA_SIZE, 0.01);

        // 生成预热数据
        Set<String> warmupData = generateRandomKeys(WARMUP_DATA_SIZE);

        // 预热布隆过滤器
        long startTime = System.currentTimeMillis();
        boolean allAdded = bloomFilterService.addAll(warmupData);
        long warmupTime = System.currentTimeMillis() - startTime;

        System.out.printf("预热完成,添加了 %d 个元素,耗时 %d ms,全部添加成功: %b%n",
                WARMUP_DATA_SIZE, warmupTime, allAdded);

        // 生成测试数据
        TestData testData = generateTestData(warmupData);

        // 执行测试
        TestResult result = executeTest(bloomFilterService, testData);

        // 计算并输出准确率
        printTestResult(result);
    }

    /**
     * 生成随机的字符串作为测试key
     *
     * @param count 生成的key数量
     * @return 包含指定数量随机key的集合
     */
    private static Set<String> generateRandomKeys(int count) {
        Set<String> keys = new HashSet<>(count);
        Random random = new Random();
        while (keys.size() < count) {
            // 生成随机的10位数字作为key
            int number = random.nextInt(1000000000); // 生成0到999999999之间的随机数
            String key = String.format("key_%010d", number);
            keys.add(key);
        }

        return keys;
    }

    /**
     * 生成测试数据,包含一部分存在于预热数据中的key和一部分不存在的key
     *
     * @param warmupData 预热数据集合
     * @return 测试数据对象,包含测试key列表和每个key是否真实存在的标记
     */
    private static TestData generateTestData(Set<String> warmupData) {
        List<String> testKeys = new ArrayList<>(TEST_DATA_SIZE);
        List<Boolean> actualExists = new ArrayList<>(TEST_DATA_SIZE);
        Random random = new Random();

        // 从预热数据中选取一部分作为存在的测试数据
        int existCount = (int) (TEST_DATA_SIZE * EXIST_RATIO);
        List<String> existingKeys = new ArrayList<>(warmupData).subList(0, existCount);

        // 添加存在的测试数据
        for (String key : existingKeys) {
            testKeys.add(key);
            actualExists.add(true);
        }

        // 生成不存在的测试数据
        Set<String> nonExistingKeys = new HashSet<>();
        while (nonExistingKeys.size() < TEST_DATA_SIZE - existCount) {
            String key = generateRandomKey(random);
            if (!warmupData.contains(key)) {
                nonExistingKeys.add(key);
            }
        }
        // 添加不存在的测试数据
        for (String key : nonExistingKeys) {
            testKeys.add(key);
            actualExists.add(false);
        }
        return new TestData(testKeys, actualExists);
    }

    /**
     * 生成单个随机key
     *
     * @param random 随机数生成器
     * @return 随机生成的key
     */
    private static String generateRandomKey(Random random) {

        int number = random.nextInt(1000000000); // 生成0到999999999之间的随机数
        return String.format("key_%010d", number);


    }
    /**
     * 执行布隆过滤器测试
     * @param bloomFilterService 布隆过滤器服务
     * @param testData           测试数据
     * @return 测试结果,包含各种统计信息
     */
    private static TestResult executeTest(RedissonBloomFilterService bloomFilterService, TestData testData) {
        int truePositive = 0;  // 实际存在,判断存在
        int trueNegative = 0;  // 实际不存在,判断不存在
        int falsePositive = 0; // 实际不存在,判断存在(误判)
        int falseNegative = 0; // 实际存在,判断不存在(漏判)

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < testData.testKeys.size(); i++) {
            String key = testData.testKeys.get(i);
            boolean actual = testData.actualExists.get(i);
            boolean predicted = bloomFilterService.contains(key);

            if (actual) {
                if (predicted) {
                    truePositive++;
                } else {
                    falseNegative++;
                }
            } else {
                if (predicted) {
                    falsePositive++;
                } else {
                    trueNegative++;
                }
            }
        }

        long testTime = System.currentTimeMillis() - startTime;

        return new TestResult(
                truePositive, trueNegative,
                falsePositive, falseNegative,
                testTime
        );
    }

    /**
     * 打印测试结果
     *
     * @param result 测试结果对象
     */
    private static void printTestResult(TestResult result) {
        System.out.println("\n测试结果统计:");
        System.out.printf("测试数据总数: %d%n", TEST_DATA_SIZE);
        System.out.printf("实际存在的key数量: %d%n", result.truePositive + result.falseNegative);
        System.out.printf("实际不存在的key数量: %d%n", result.trueNegative + result.falsePositive);
        System.out.println("-----");
        System.out.printf("真阳性 (实际存在,判断存在): %d%n", result.truePositive);
        System.out.printf("真阴性 (实际不存在,判断不存在): %d%n", result.trueNegative);
        System.out.printf("假阳性 (实际不存在,判断存在 - 误判): %d%n", result.falsePositive);
        System.out.printf("假阴性 (实际存在,判断不存在 - 漏判): %d%n", result.falseNegative);
        System.out.println("-----");

        // 计算准确率:(真阳性 + 真阴性) / 总测试数
        double accuracy = (double) (result.truePositive + result.trueNegative) / TEST_DATA_SIZE;
        // 计算精确率:真阳性 / (真阳性 + 假阳性)
        double precision = result.truePositive + result.falsePositive > 0
                ? (double) result.truePositive / (result.truePositive + result.falsePositive)
                : 1.0;
        // 计算召回率:真阳性 / (真阳性 + 假阴性)
        double recall = result.truePositive + result.falseNegative > 0
                ? (double) result.truePositive / (result.truePositive + result.falseNegative)
                : 1.0;

        System.out.printf("准确率: %.2f%%%n", accuracy * 100);
        System.out.printf("精确率: %.2f%%%n", precision * 100);
        System.out.printf("召回率: %.2f%%%n", recall * 100);
        System.out.printf("测试耗时: %d ms%n", result.testTime);
    }

    /**
     * 测试数据封装类
     */
    private static class TestData {
        List<String> testKeys;
        List<Boolean> actualExists;

        TestData(List<String> testKeys, List<Boolean> actualExists) {
            this.testKeys = testKeys;
            this.actualExists = actualExists;
        }
    }

    /**
     * 测试结果封装类
     */
    private static class TestResult {
        int truePositive;   // 真阳性
        int trueNegative;   // 真阴性
        int falsePositive;  // 假阳性
        int falseNegative;  // 假阴性
        long testTime;      // 测试耗时(ms)

        TestResult(int truePositive, int trueNegative,
                   int falsePositive, int falseNegative,
                   long testTime) {
            this.truePositive = truePositive;
            this.trueNegative = trueNegative;
            this.falsePositive = falsePositive;
            this.falseNegative = falseNegative;
            this.testTime = testTime;
        }
    }
}

在这里插入图片描述

测试结果

-----
预热完成,添加了 100000 个元素,耗时 7383 ms,全部添加成功: false

测试结果统计:
测试数据总数: 100
实际存在的key数量: 50
实际不存在的key数量: 50
-----
真阳性 (实际存在,判断存在): 50
真阴性 (实际不存在,判断不存在): 39
假阳性 (实际不存在,判断存在 - 误判): 11
假阴性 (实际存在,判断不存在 - 漏判): 0
-----
准确率: 89.00%
精确率: 81.97%
召回率: 100.00%
测试耗时: 10 ms
-----

如果文章对你有一点点帮助,欢迎【点赞、留言、+ 关注】
您的关注是我创作的动力!若有疑问/交流/需求,欢迎留言/私聊!
多一个朋友多一条路!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值