在高并发系统中,我们经常面临这样的挑战:如何快速判断一个元素是否存在于海量数据集合中?传统方案如数据库查询、Redis 缓存查询虽然可行,但在面对 缓存穿透 、恶意请求攻击 等场景时,性能瓶颈和资源消耗问题尤为突出。
布隆过滤器(Bloom Filter)应运而生——一种 空间效率极高的概率型数据结构 ,核心特点是:如果判断一个元素“不存在”,那么它一定不存在(100%准确);如果判断“存在”,则可能存在(有一定误判率) 。它能在常数时间内判断元素是否存在,且内存消耗极低。虽然存在一定的误判率(假阳性),但其"宁可错放,绝不漏判"的特性,使其成为系统防护的第一道防线。
布隆过滤器(Bloom Filter)是1970年由 Burton Howard Bloom 提出的一种 空间效率极高的概率型数据结构 ,用于快速判断一个元素是否在一个集合中。
添加元素 :
查询元素 :
| 特性 | 说明 | 影响 | | ---
| 空间效率高 | 仅需位数组存储哈希标记 | 存储1亿元素仅需约1GB 内存(误判率1%) | | 查询速度快 | 时间复杂度 O(k) | 适合高并发场景 | | 存在误判率 | 如果判断“存在”,则可能存在(有一定误判率) | 误判率可控制,通常0.1%-1% | | 无假阴性 | 如果判断一个元素“不存在”,那么它一定不存在(100%准确) | 保证数据安全 | | 不支持删除 | 删除元素会影响其他元素判断 | 需使用计数布隆过滤器变种 |
误判率 p 与参数关系:
公式:p ≈ (1 - e^(-kn/m))^k
实际使用中,可根据预期元素数量 n 和可接受误判率 p 计算最优的 m 和 k 值。
Redis 4.0+通过模块机制支持布隆过滤器,需安装 RedisBloom 模块。
安装方式 :
# Docker方式(推荐)
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
# 手动编译
git clone https://github.com/RedisBloom/RedisBloom.git cd RedisBloom make redis-server --loadmodule ./redisbloom.so
常用命令 :
# 创建布隆过滤器
BF.RESERVE user_filter 0.001 1000000 # key, 误判率0.1%, 容量100万
# 添加元素
BF.ADD user_filter user:1001 BF.MADD user_filter user:1002 user:1003 # 批量添加
# 查询元素
BF.EXISTS user_filter user:1001 # 返回1(可能存在)
BF.EXISTS user_filter user:9999 # 返回0(一定不存在)
# 查看过滤器信息
BF.INFO user_filter
Redisson 是 Redis 的 Java 客户端,提供了完整的布隆过滤器 API 封装。
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.redisson
redisson-spring-boot-starter
3.24.1
# application.yml
spring: redis: host: localhost port: 6379 database: 0 # password: your_password # 如有密码需配置
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration public class BloomFilterConfig { @Bean public RBloomFilter userBloomFilter(RedissonClient redissonClient) { // 获取或创建布隆过滤器 RBloomFilter bloomFilter = redissonClient.getBloomFilter("userBloomFilter"); // 初始化:预计元素数量100万,误判率0.1% bloomFilter.tryInit(1000000L, 0.001); return bloomFilter; } @Bean public RBloomFilter productBloomFilter(RedissonClient redissonClient) { RBloomFilter bloomFilter = redissonClient.getBloomFilter("productBloomFilter"); bloomFilter.tryInit(500000L, 0.001); // 50万商品,误判率0.1% return bloomFilter; } }
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component public class BloomFilterInitializer implements CommandLineRunner { @Autowired private RBloomFilter userBloomFilter; @Autowired private UserRepository userRepository; @Override public void run(String... args) throws Exception { // 应用启动时,将数据库中的有效用户 ID 预热到布隆过滤器 List allUserIds = userRepository.findAllUserIds(); for (String userId : allUserIds) { userBloomFilter.add(userId); } System.out.println("布隆过滤器预热完成,已添加 " + allUserIds.size() + " 个用户 ID"); } }
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service public class UserService { @Autowired private RBloomFilter userBloomFilter; @Autowired private RedisTemplate redisTemplate; @Autowired private UserRepository userRepository; // 缓存过期时间 private static final long CACHE_EXPIRE_SECONDS = 30 * 60; // 30分钟 /** * 三级防护:布隆过滤器 → Redis 缓存 → 数据库 */ public User getUserById(String userId) { // 1. 第一级:布隆过滤器预检 if (!userBloomFilter.contains(userId)) { // 布隆过滤器判定一定不存在,直接返回,避免穿透数据库 log.warn("布隆过滤器拦截无效用户 ID: {}", userId); return null; } // 2. 第二级:查询 Redis 缓存 String cacheKey = "user:" + userId; User user = (User) redisTemplate.opsForValue().get(cacheKey); if (user != null) { return user; // 缓存命中,直接返回 } // 3. 第三级:查询数据库 user = userRepository.findById(userId).orElse(null); if (user != null) { // 数据库存在,写入 Redis 缓存 redisTemplate.opsForValue().set( cacheKey, user, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS ); } else { // 数据库不存在,缓存空值(短期),防止缓存穿透 // 注意:这种情况是布隆过滤器误判,实际数据不存在 redisTemplate.opsForValue().set( cacheKey, new User(), // 空对象或特定标记 5, // 短时间,如5分钟 TimeUnit.MINUTES ); } return user; } /** * 新增用户时,同步更新布隆过滤器 */ public void addUser(User user) { // 1. 保存到数据库 userRepository.save(user); // 2. 添加到布隆过滤器 userBloomFilter.add(user.getId()); // 3. 清除 Redis 缓存(如有) redisTemplate.delete("user:" + user.getId()); } }
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{userId}") public ResponseEntity<?> getUser(@PathVariable String userId) { User user = userService.getUserById(userId); if (user == null) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Result.error("用户不存在")); } return ResponseEntity.ok(Result.success(user)); } @PostMapping public ResponseEntity<?> createUser(@RequestBody User user) { userService.addUser(user); return ResponseEntity.ok(Result.success("用户创建成功")); } }
| 场景 | 描述 | 优势 | | ---
| 缓存穿透防护 | 拦截查询不存在数据的恶意请求 | 减少99%无效数据库查询 | | 用户注册去重 | 快速判断用户名/手机号是否已注册 | 避免全表扫描,提升注册性能 | | 爬虫 URL 去重 | 避免重复爬取同一 URL | 500万 URL 仅需约6MB 内存 | | 推荐系统过滤 | 过滤已推荐内容,避免重复推荐 | 提升用户体验和推荐效果 | | 垃圾邮件过滤 | 快速判断发件人是否在黑名单 | 毫秒级响应,支持海量名单 |
// 参数计算公式
public class BloomFilterCalculator {
/**
* 计算最优位数组长度
* @param n 预期元素数量
* @param p 可接受误判率
*/
public static long optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}
/**
* 计算最优哈希函数个数
* @param n 预期元素数量
* @param m 位数组长度
*/
public static int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
}
| 方案 | 优点 | 缺点 | 适用场景 | | ---
| RedisBloom 模块 | 原生支持、性能最优、命令丰富 | 需额外安装模块 | 生产环境、性能要求高 | | Redisson 客户端 | 开箱即用、Java 友好、功能全面 | 依赖 Redisson 生态 | Spring Boot 项目、快速开发 | | 自定义 Bitmap 实现 | 灵活可控、深入理解原理 | 开发维护成本高 | 学习研究、特殊需求 | | Guava 本地实现 | 零依赖、单机性能好 | 不支持分布式、无持久化 | 单机应用、测试环境 |
@Service
public class BloomFilterMonitor {
@Autowired
private RedissonClient redissonClient;
/**
* 获取布隆过滤器状态
*/
public Map getBloomFilterStatus(String filterName) {
RBloomFilter<?> bloomFilter = redissonClient.getBloomFilter(filterName);
Map status = new HashMap<>();
status.put("name", filterName);
status.put("expectedInsertions", bloomFilter.getExpectedInsertions());
status.put("falseProbability", bloomFilter.getFalseProbability());
status.put("size", bloomFilter.count()); // 实际插入数量
// 计算当前误判率(估算)
double currentErrorRate = calculateCurrentErrorRate(bloomFilter);
status.put("currentErrorRate", currentErrorRate);
return status;
}
}
# 查看Redis中所有布隆过滤器
redis-cli keys "*bloom*"
# 查看特定过滤器信息
redis-cli BF.INFO userBloomFilter
# 手动添加测试数据
redis-cli BF.ADD userBloomFilter "test_user_001"
# 性能测试:批量查询
redis-cli --pipe < query_commands.txt
布隆过滤器通过 空间换时间 的策略,在 Redis 和 Spring Boot 项目中提供了高效的 存在性判断 解决方案。在实际项目中:
通过以上方案,可以在高并发、海量数据的场景下,以极小的内存代价实现高效的数据过滤和防护,显著提升系统性能和稳定性。
更新时间:2026-02-27
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight All Rights Reserved.
Powered By 61893.com 闽ICP备11008920号
闽公网安备35020302035593号