Redis + 布隆过滤器解决缓存穿透问题
1. Redis + 布隆过滤器解决缓存穿透问题
📌 什么是缓存穿透?
缓存穿透指的是查询的数据既不在缓存,也不在数据库,导致每次查询都直接访问数据库,增加数据库压力。
例如:攻击者故意请求不存在的 ID,导致大量无效查询,冲垮数据库。
解决方案:
普通缓存机制:数据库查询为空时,写入一个短期过期的空值,但对高并发请求不够高效。
布隆过滤器方案
(推荐):
- 布隆过滤器预存已有数据的 key。
- 查询前,先通过布隆过滤器判断 key 是否可能存在:
- 存在 → 查询 Redis 缓存,未命中则查询数据库,再写入缓存。
- 不存在 → 直接返回,避免访问数据库,防止缓存穿透。
2. Java 代码示例:Redis + 布隆过滤器
📌 依赖
使用 Redisson 库来操作 Redis 的布隆过滤器,需要添加以下依赖(使用 Maven
或 Gradle
)。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.21.1</version>
</dependency>
📌 代码示例
🚀 1. 初始化 Redis 连接
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedisBloomFilter {
private static RedissonClient redissonClient;
private static RBloomFilter<String> bloomFilter;
static {
// 配置 Redis 连接
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379"); // 连接本地 Redis
redissonClient = Redisson.create(config);
// 初始化布隆过滤器
bloomFilter = redissonClient.getBloomFilter("product_bloom_filter");
bloomFilter.tryInit(1000000L, 0.01); // 预估 100w 个元素,误判率 1%
}
/**
* 向布隆过滤器添加数据
*/
public static void addToBloomFilter(String productId) {
bloomFilter.add(productId);
}
/**
* 判断数据是否可能存在
*/
public static boolean mightContain(String productId) {
return bloomFilter.contains(productId);
}
public static void main(String[] args) {
// 模拟初始化布隆过滤器
addToBloomFilter("1001");
addToBloomFilter("1002");
addToBloomFilter("1003");
// 查询数据
System.out.println("1001 存在?" + mightContain("1001")); // true
System.out.println("2001 存在?" + mightContain("2001")); // false
}
}
🚀 2. 结合 Redis 缓存 + MySQL 数据库
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import redis.clients.jedis.Jedis;
public class BloomFilterCacheDemo {
private static Jedis redisClient = new Jedis("127.0.0.1", 6379);
private static RedissonClient redissonClient;
private static RBloomFilter<String> bloomFilter;
static {
// 配置 Redisson 连接 Redis
redissonClient = Redisson.create();
bloomFilter = redissonClient.getBloomFilter("product_bloom_filter");
bloomFilter.tryInit(1000000L, 0.01); // 100w 数据,误判率 1%
}
/**
* 模拟查询数据库
*/
public static String queryDatabase(String productId) {
if ("1001".equals(productId)) {
return "商品1001:iPhone 15";
} else if ("1002".equals(productId)) {
return "商品1002:MacBook Pro";
}
return null; // 模拟数据库查询不到
}
/**
* 查询商品详情(使用布隆过滤器+Redis缓存)
*/
public static String getProductInfo(String productId) {
// 1️⃣ 先查询布隆过滤器
if (!bloomFilter.contains(productId)) {
return "商品不存在"; // 直接返回,防止缓存穿透
}
// 2️⃣ 查询 Redis 缓存
String cacheData = redisClient.get("product:" + productId);
if (cacheData != null) {
return "【缓存】" + cacheData;
}
// 3️⃣ 查询数据库
String dbData = queryDatabase(productId);
if (dbData != null) {
// 写入 Redis,设置 10 分钟过期
redisClient.setex("product:" + productId, 600, dbData);
return "【数据库】" + dbData;
}
return "商品不存在";
}
public static void main(String[] args) {
// 初始化布隆过滤器(模拟数据库已有商品)
bloomFilter.add("1001");
bloomFilter.add("1002");
// 测试查询
System.out.println(getProductInfo("1001")); // 【数据库】商品1001:iPhone 15
System.out.println(getProductInfo("2001")); // 商品不存在
}
}
3. 运行结果
查询商品 ID | Redis 缓存 | 数据库 | 布隆过滤器 | 返回结果 |
---|---|---|---|---|
1001 |
❌ 无 | ✅ 有 | ✅ 可能存在 | 【数据库】商品1001:iPhone 15 |
1001 (第二次查询) |
✅ 有 | - | ✅ 可能存在 | 【缓存】商品1001:iPhone 15 |
2001 (不存在) |
❌ 无 | ❌ 无 | ❌ 一定不存在 | 商品不存在 |
4. 关键点总结
✅ 布隆过滤器防止缓存穿透
- 先用布隆过滤器检查,如果 不在过滤器内,直接返回,避免查询数据库。
✅ Redis 作为缓存
- 查询时,优先访问 Redis,如果缓存未命中,才访问数据库,并写入 Redis 进行缓存。
✅ 误判率低
- 通过
bloomFilter.tryInit(1000000L, 0.01)
,设置合理的n
(数据量)和p
(误判率),确保高效过滤。
✅ 不能删除数据
- 布隆过滤器不支持删除,可以用定期重建的方式优化,例如每天重新初始化。
5. 适用场景
🔹 电商系统:防止查询不存在的商品 ID,减少数据库压力。
🔹 用户系统:防止查询不存在的用户 ID,避免账号碰撞攻击。
🔹 API 限流:判断 IP 或 Token 是否在黑名单,快速拒绝非法请求。