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"); // 連接本地 RedisredissonClient = 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")); // trueSystem.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 連接 RedisredissonClient = 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 15System.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 是否在黑名單,快速拒絕非法請求。