在電商系統中,商品詳情頁是一個典型的高頻訪問場景。當用戶請求某個商品的詳情時,系統會優先從緩存中獲取數據。如果緩存中沒有該商品的詳情,系統會去數據庫查詢并更新緩存。然而,如果某個熱門商品的緩存失效,大量請求會同時查詢數據庫,導致數據庫壓力驟增,這就是緩存擊穿問題。
以下是一個結合布隆過濾器防止緩存擊穿的Java偽代碼實現案例:
場景描述
商品詳情查詢:用戶通過商品ID查詢商品詳情。
緩存層:使用Redis作為緩存,存儲商品詳情。
布隆過濾器:使用RedisBloom模塊實現布隆過濾器,存儲所有可能被查詢的商品ID。
數據庫層:存儲商品詳情的數據庫。
實現思路
初始化布隆過濾器:
在系統啟動時,將數據庫中所有商品的ID插入到布隆過濾器中。
查詢流程:
當用戶請求商品詳情時,先通過布隆過濾器判斷該商品ID是否存在。
如果布隆過濾器判斷不存在,則直接返回“商品不存在”。
如果布隆過濾器判斷可能存在,則去緩存中查詢。
如果緩存中有數據,則直接返回緩存結果。
如果緩存中沒有數據,則去數據庫查詢,并將結果放入緩存。
Java偽代碼實現
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.bloom.BloomOptions;
import io.lettuce.core.bloom.RedisBloomCommands;import java.util.concurrent.locks.ReentrantLock;public class ProductService {// Redis客戶端private RedisClient redisClient;private RedisCommands<String, String> syncCommands;private RedisBloomCommands<String, String> bloomCommands;// 數據庫客戶端private DatabaseClient databaseClient;// 布隆過濾器的Keyprivate static final String BLOOM_FILTER_KEY = "product:bloomfilter";// 緩存Key前綴private static final String CACHE_KEY_PREFIX = "product:cache:";// 鎖,用于防止緩存擊穿時的并發問題private ReentrantLock lock = new ReentrantLock();public ProductService(RedisClient redisClient, DatabaseClient databaseClient) {this.redisClient = redisClient;this.syncCommands = redisClient.connect().sync();this.bloomCommands = redisClient.connect().sync();this.databaseClient = databaseClient;}// 初始化布隆過濾器public void initBloomFilter() {// 獲取所有商品IDList<String> productIds = databaseClient.getAllProductIds();// 初始化布隆過濾器bloomCommands.bfCreate(BLOOM_FILTER_KEY, BloomOptions.defaults(), productIds.size());// 將所有商品ID插入布隆過濾器for (String productId : productIds) {bloomCommands.bfAdd(BLOOM_FILTER_KEY, productId);}}// 查詢商品詳情public Product getProductDetails(String productId) {// 1. 使用布隆過濾器判斷商品ID是否存在boolean exists = bloomCommands.bfExists(BLOOM_FILTER_KEY, productId);if (!exists) {// 如果布隆過濾器判斷不存在,直接返回商品不存在return null;}// 2. 從緩存中查詢商品詳情String cacheKey = CACHE_KEY_PREFIX + productId;String productDetails = syncCommands.get(cacheKey);if (productDetails != null) {// 如果緩存中有數據,直接返回return new Product(productDetails);}// 3. 緩存中沒有數據,加鎖防止緩存擊穿lock.lock();try {// 再次檢查緩存,防止并發問題productDetails = syncCommands.get(cacheKey);if (productDetails != null) {return new Product(productDetails);}// 4. 查詢數據庫Product product = databaseClient.getProductById(productId);if (product != null) {// 將查詢結果放入緩存syncCommands.set(cacheKey, product.toJson());}return product;} finally {lock.unlock();}}
}// 數據庫客戶端
class DatabaseClient {// 獲取所有商品IDpublic List<String> getAllProductIds() {// 查詢數據庫,返回所有商品IDreturn database.query("SELECT id FROM products");}// 根據商品ID查詢商品詳情public Product getProductById(String productId) {// 查詢數據庫,返回商品詳情return database.query("SELECT * FROM products WHERE id = ?", productId);}
}// 商品類
class Product {private String id;private String name;private double price;public Product(String details) {// 從JSON字符串解析商品詳情this.id = parseId(details);this.name = parseName(details);this.price = parsePrice(details);}public String toJson() {// 將商品詳情轉換為JSON字符串return "{\"id\":\"" + id + "\",\"name\":\"" + name + "\",\"price\":" + price + "}";}
}
代碼說明
布隆過濾器初始化:
在系統啟動時,調用initBloomFilter方法,將所有商品ID插入到布隆過濾器中。
查詢流程:
使用布隆過濾器判斷商品ID是否存在。
如果布隆過濾器判斷不存在,則直接返回null。
如果布隆過濾器判斷可能存在,則去緩存中查詢。
如果緩存中沒有數據,則加鎖并查詢數據庫,將結果放入緩存。
鎖機制:
使用ReentrantLock防止緩存擊穿時的并發問題。
在加鎖后再次檢查緩存,確保只有一個線程去查詢數據庫。
優點
減少無效查詢:布隆過濾器可以快速判斷商品ID是否存在,減少對不存在商品的查詢。
減輕數據庫壓力:即使緩存失效,也能通過布隆過濾器減少對數據庫的直接查詢。
缺點
布隆過濾器誤判:雖然誤判率可以通過調整參數降低,但無法完全避免。
鎖機制的開銷:在高并發場景下,鎖可能會成為性能瓶頸。
通過以上實現,電商系統可以在商品詳情查詢場景中有效緩解緩存擊穿問題,同時結合布隆過濾器減少對數據庫的無效查詢。