前言
在日常与众多使用 Redis 的开发者交流中,我们时常会遇到一些极具参考价值的实践案例。本文所分享的,正是我们一位客户在真实业务场景中,通过巧妙使用 Redis 布隆过滤器(Bloom Filter),成功化解千万级查询压力的实战经历。
这个案例并非来自我们的直接设计,而是客户在其业务演进过程中的一次精彩探索。我们觉得其思路清晰、方案通用,且成效显著,非常有借鉴意义,因此特别征得客户同意,将这份经验整理成文,与各位开发者共享。
希望这个优化历程能为正在面临高并发查询瓶颈的团队提供一种可行的解决思路。如果在 Redis 使用中有任何疑问或独特的实践,也欢迎与我们交流探讨。
从注册接口开始的性能瓶颈
项目初期,用户注册逻辑非常简单:在插入新用户前,先执行一条 SQL 查询判断用户名是否已被占用:
SELECT COUNT(*) FROM users WHERE username = '张三';
在用户量只有几千时,这样的设计并无压力。但随着用户规模迅速增长,系统开始暴露出性能问题:CPU 使用率急剧上升、接口响应变慢、数据库连接池频繁告急。
经过排查,我们发现每次注册请求都会触发这条“用户名是否已存在”的查询,导致数据库被频繁访问。这条看似简单的 SQL,竟成了整个注册流程的性能瓶颈。
为什么数据库不适合做“存在性检查”
即便我们为 username 字段添加了索引,当数据量达到千万级别时,问题依然存在。原因在于:
-
每次请求仍须访问数据库;
-
数据库的水平扩展并不容易;
-
缓存也难以高效存储数百万个用户名。
我们意识到,数据库不应承担这种“是否已存在”的检查任务。如果能在请求到达数据库之前,就过滤掉大部分明显不存在的用户名,就能大幅减轻数据库的负担。
于是,我们引入了布隆过滤器(Bloom Filter)。
哨兵
布隆过滤器是一种高效的概率型数据结构,它不能确定某个元素“一定存在”,但能准确判断某个元素“肯定不存在”。 简单来说,它的判断逻辑无非分为两种:
-
肯定不存在 → 无需查询数据库;
-
可能存在 → 再交由数据库确认。
在实际场景中,超过九成的请求都属于“肯定不存在”的情况,因此数据库的查询压力将大幅降低。
为什么选择 Redis
布隆过滤器是一种算法思想,需要具体的存储实现。Redis 是理想的实现载体,原因如下:
-
基于内存,访问速度快;
-
提供 RedisBloom 模块,也可通过位操作自行实现;
-
天然支持分布式,多个服务可共享同一过滤器;
-
支持持久化,避免数据丢失。
这种组合特别适用于:
-
用户名 / 邮箱去重校验
-
防止重复交易请求
-
垃圾消息或评论识别
-
爬虫 URL 去重
基于 Spring Boot + Redis 的实现示例
以下示例展示如何在 Spring Boot 项目中集成 Redis 布隆过滤器。
Maven 依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>io.lettuce.core</groupId><artifactId>lettuce-core</artifactId></dependency></dependencies>
启动 Redis
Windows:
choco install redis
redis-server
Mac / Linux:
brew install redis
redis-server
配置文件
spring.application.name=bloom-filter-demospring.redis.host=localhostspring.redis.port=6379
BloomFilterService
@Servicepublic class BloomFilterService {private static final int SIZE = 1_000_000;private static final int[] SEEDS = {7, 11, 13, 31, 37, 61};@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private final String key = "username_bloom_filter";// 将用户名加入布隆过滤器public void add(String username) {for (int seed : SEEDS) {int hash = hash(username, seed);
redisTemplate.opsForValue().setBit(key, hash, true);}}// 检查用户名是否可能存在public boolean mightContain(String username) {for (int seed : SEEDS) {int hash = hash(username, seed);
Boolean bit = redisTemplate.opsForValue().getBit(key, hash);if (bit == null || !bit) return false;}return true;}private int hash(String username, int seed) {int result = 0;byte[] bytes = username.getBytes(StandardCharsets.UTF_8);for (byte b : bytes) result = seed * result + b;return Math.abs(result % SIZE);}}
Controller
@Servicepublic class BloomFilterService {private static final int SIZE = 1_000_000;private static final int[] SEEDS = {7, 11, 13, 31, 37, 61};@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private final String key = "username_bloom_filter";// 将用户名加入布隆过滤器public void add(String username) {for (int seed : SEEDS) {int hash = hash(username, seed);
redisTemplate.opsForValue().setBit(key, hash, true);}}// 检查用户名是否可能存在public boolean mightContain(String username) {for (int seed : SEEDS) {int hash = hash(username, seed);
Boolean bit = redisTemplate.opsForValue().getBit(key, hash);if (bit == null || !bit) return false;}return true;}private int hash(String username, int seed) {int result = 0;byte[] bytes = username.getBytes(StandardCharsets.UTF_8);for (byte b : bytes) result = seed * result + b;return Math.abs(result % SIZE);}}
接口测试
# 添加用户名
curl -X POST http://localhost:8080/usernames/add/praveencodes
# 检查用户名是否存在
curl http://localhost:8080/usernames/check/praveencodes
# 检查随机用户名
curl http://localhost:8080/usernames/check/randomuser
性能对比测试
我们在 100 万用户名的数据集上进行了性能测试,结果如下:
| 项目 | 传统数据库查询 | Redis 布隆过滤器预检 |
| 单次查询耗时 | 约 3~5 ms | 约 0.05 ms |
| 数据库访问次数 | 1000 万次 | 约 10 万次 |
| 内存占用 | 高(依赖索引) | 几 MB |
测试结果很明显,布隆过滤器将数据库查询量降低了两个数量级,性能提升显著。
局限
布隆过滤器虽好,但也有其适用边界:
-
存在误判可能:它可能将不存在的元素误判为“可能存在”,但绝不会将存在的元素判为“不存在”。
-
不支持删除操作:标准布隆过滤器无法删除已添加的元素。如需删除,可考虑使用 Counting Bloom Filter。
-
参数需合理配置:位数组大小与哈希函数数量应根据数据规模调整,以平衡误判率与内存使用。
合理分工,让数据库回归该干的事
通过在注册流程中引入 Redis 布隆过滤器,将数据库的查询压力从十万级降至万级以下。这种优化思路不仅适用于用户注册,也可广泛应用于防重复提交、内容去重、垃圾识别等场景。
在高并发系统中,数据库应专注于持久化与事务处理,而布隆过滤器则适合作为前置过滤层,拦截大量无效请求,让数据库做它最擅长的事。
480

被折叠的 条评论
为什么被折叠?



