Redis預(yù)防緩存穿透的6種策略
在高并發(fā)系統(tǒng)中,Redis作為緩存中間件已成為標(biāo)配,它能有效減輕數(shù)據(jù)庫(kù)壓力、提升系統(tǒng)響應(yīng)速度。然而,緩存并非萬(wàn)能,在實(shí)際應(yīng)用中我們常常面臨一個(gè)嚴(yán)峻問(wèn)題——緩存穿透。
這種現(xiàn)象可能導(dǎo)致Redis失效,使大量請(qǐng)求直接沖擊數(shù)據(jù)庫(kù),造成系統(tǒng)性能急劇下降甚至宕機(jī)。
緩存穿透原理分析
什么是緩存穿透
緩存穿透是指查詢(xún)一個(gè)根本不存在的數(shù)據(jù),由于緩存不命中,請(qǐng)求會(huì)穿透緩存層直接訪問(wèn)數(shù)據(jù)庫(kù)。這種情況下,數(shù)據(jù)庫(kù)也無(wú)法查詢(xún)到對(duì)應(yīng)數(shù)據(jù),因此無(wú)法將結(jié)果寫(xiě)入緩存,導(dǎo)致每次同類(lèi)請(qǐng)求都會(huì)重復(fù)訪問(wèn)數(shù)據(jù)庫(kù)。
典型場(chǎng)景與危害
Client ---> Redis(未命中) ---> Database(查詢(xún)無(wú)果) ---> 不更新緩存 ---> 循環(huán)重復(fù)
緩存穿透的主要危害:
- 數(shù)據(jù)庫(kù)壓力激增:大量無(wú)效查詢(xún)直接落到數(shù)據(jù)庫(kù)
- 系統(tǒng)響應(yīng)變慢:數(shù)據(jù)庫(kù)負(fù)載過(guò)高導(dǎo)致整體性能下降
- 資源浪費(fèi):無(wú)謂的查詢(xún)消耗CPU和IO資源
- 安全風(fēng)險(xiǎn):可能被惡意利用作為拒絕服務(wù)攻擊的手段
緩存穿透通常有兩種情況:
- 正常業(yè)務(wù)查詢(xún):查詢(xún)的數(shù)據(jù)確實(shí)不存在
- 惡意攻擊:故意構(gòu)造不存在的key進(jìn)行大量請(qǐng)求
下面介紹六種有效的防范策略。
策略一:空值緩存
原理
空值緩存是最簡(jiǎn)單直接的防穿透策略。當(dāng)數(shù)據(jù)庫(kù)查詢(xún)不到某個(gè)key對(duì)應(yīng)的值時(shí),我們?nèi)匀粚⑦@個(gè)"空結(jié)果"緩存起來(lái)(通常以null值或特定標(biāo)記表示),并設(shè)置一個(gè)相對(duì)較短的過(guò)期時(shí)間。這樣,下次請(qǐng)求同一個(gè)不存在的key時(shí),可以直接從緩存返回"空結(jié)果",避免再次查詢(xún)數(shù)據(jù)庫(kù)。
實(shí)現(xiàn)示例
@Service public class UserServiceImpl implements UserService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private UserMapper userMapper; private static final String KEY_PREFIX = "user:"; private static final String EMPTY_VALUE = "{}"; // 空值標(biāo)記 private static final long EMPTY_VALUE_EXPIRE_SECONDS = 300; // 空值過(guò)期時(shí)間 private static final long NORMAL_EXPIRE_SECONDS = 3600; // 正常值過(guò)期時(shí)間 @Override public User getUserById(Long userId) { String redisKey = KEY_PREFIX + userId; // 1. 查詢(xún)緩存 String userJson = redisTemplate.opsForValue().get(redisKey); // 2. 緩存命中 if (userJson != null) { // 判斷是否為空值 if (EMPTY_VALUE.equals(userJson)) { return null; // 返回空結(jié)果 } // 正常緩存,反序列化并返回 return JSON.parseObject(userJson, User.class); } // 3. 緩存未命中,查詢(xún)數(shù)據(jù)庫(kù) User user = userMapper.selectById(userId); // 4. 寫(xiě)入緩存 if (user != null) { // 數(shù)據(jù)庫(kù)查到數(shù)據(jù),寫(xiě)入正常緩存 redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(user), NORMAL_EXPIRE_SECONDS, TimeUnit.SECONDS); } else { // 數(shù)據(jù)庫(kù)未查到數(shù)據(jù),寫(xiě)入空值緩存 redisTemplate.opsForValue().set(redisKey, EMPTY_VALUE, EMPTY_VALUE_EXPIRE_SECONDS, TimeUnit.SECONDS); } return user; } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 實(shí)現(xiàn)簡(jiǎn)單,無(wú)需額外組件
- 對(duì)系統(tǒng)侵入性低
- 立竿見(jiàn)影的效果
缺點(diǎn)
- 可能會(huì)占用較多的緩存空間
- 如果空值較多,可能導(dǎo)致緩存效率下降
- 無(wú)法應(yīng)對(duì)大規(guī)模的惡意攻擊
- 短期內(nèi)可能造成數(shù)據(jù)不一致(新增數(shù)據(jù)后緩存依然返回空值)
策略二:布隆過(guò)濾器
原理
布隆過(guò)濾器(Bloom Filter)是一種空間效率很高的概率型數(shù)據(jù)結(jié)構(gòu),用于檢測(cè)一個(gè)元素是否屬于一個(gè)集合。它的特點(diǎn)是存在誤判,即可能會(huì)將不存在的元素誤判為存在(false positive),但不會(huì)將存在的元素誤判為不存在(false negative)。
布隆過(guò)濾器包含一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列哈希函數(shù)。當(dāng)插入一個(gè)元素時(shí),使用各個(gè)哈希函數(shù)計(jì)算該元素的哈希值,并將二進(jìn)制向量中相應(yīng)位置置為1。查詢(xún)時(shí),同樣計(jì)算哈希值并檢查向量中對(duì)應(yīng)位置,如果有任一位為0,則元素必定不存在;如果全部位都為1,則元素可能存在。
實(shí)現(xiàn)示例
使用Redis的布隆過(guò)濾器模塊(Redis 4.0+支持模塊擴(kuò)展,需安裝RedisBloom):
@Service public class ProductServiceWithBloomFilter implements ProductService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private ProductMapper productMapper; private static final String BLOOM_FILTER_NAME = "product_filter"; private static final String CACHE_KEY_PREFIX = "product:"; private static final long CACHE_EXPIRE_SECONDS = 3600; // 初始化布隆過(guò)濾器,可在應(yīng)用啟動(dòng)時(shí)執(zhí)行 @PostConstruct public void initBloomFilter() { // 判斷布隆過(guò)濾器是否存在 Boolean exists = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.exists(BLOOM_FILTER_NAME.getBytes())); if (Boolean.FALSE.equals(exists)) { // 創(chuàng)建布隆過(guò)濾器,預(yù)計(jì)元素量為100萬(wàn),錯(cuò)誤率為0.01 redisTemplate.execute((RedisCallback<Object>) connection -> connection.execute("BF.RESERVE", BLOOM_FILTER_NAME.getBytes(), "0.01".getBytes(), "1000000".getBytes())); // 加載所有商品ID到布隆過(guò)濾器 List<Long> allProductIds = productMapper.getAllProductIds(); for (Long id : allProductIds) { redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.execute("BF.ADD", BLOOM_FILTER_NAME.getBytes(), id.toString().getBytes()) != 0); } } } @Override public Product getProductById(Long productId) { String cacheKey = CACHE_KEY_PREFIX + productId; // 1. 使用布隆過(guò)濾器檢查ID是否存在 Boolean mayExist = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.execute("BF.EXISTS", BLOOM_FILTER_NAME.getBytes(), productId.toString().getBytes()) != 0); // 如果布隆過(guò)濾器判斷不存在,則直接返回 if (Boolean.FALSE.equals(mayExist)) { return null; } // 2. 查詢(xún)緩存 String productJson = redisTemplate.opsForValue().get(cacheKey); if (productJson != null) { return JSON.parseObject(productJson, Product.class); } // 3. 查詢(xún)數(shù)據(jù)庫(kù) Product product = productMapper.selectById(productId); // 4. 更新緩存 if (product != null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS); } else { // 布隆過(guò)濾器誤判,數(shù)據(jù)庫(kù)中不存在該商品 // 可以考慮記錄這類(lèi)誤判情況,優(yōu)化布隆過(guò)濾器參數(shù) log.warn("Bloom filter false positive for productId: {}", productId); } return product; } // 當(dāng)新增商品時(shí),需要將ID添加到布隆過(guò)濾器 public void addProductToBloomFilter(Long productId) { redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.execute("BF.ADD", BLOOM_FILTER_NAME.getBytes(), productId.toString().getBytes()) != 0); } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 空間效率高,內(nèi)存占用小
- 查詢(xún)速度快,時(shí)間復(fù)雜度O(k),k為哈希函數(shù)個(gè)數(shù)
- 可以有效過(guò)濾大部分不存在的ID查詢(xún)
- 可以與其他策略組合使用
缺點(diǎn)
- 存在誤判可能(false positive)
- 無(wú)法從布隆過(guò)濾器中刪除元素(標(biāo)準(zhǔn)實(shí)現(xiàn))
- 需要預(yù)先加載所有數(shù)據(jù)ID,不適合動(dòng)態(tài)變化頻繁的場(chǎng)景
- 實(shí)現(xiàn)相對(duì)復(fù)雜,需要額外維護(hù)布隆過(guò)濾器
- 可能需要定期重建以適應(yīng)數(shù)據(jù)變化
策略三:請(qǐng)求參數(shù)校驗(yàn)
原理
請(qǐng)求參數(shù)校驗(yàn)是一種在業(yè)務(wù)層面防止緩存穿透的手段。通過(guò)對(duì)請(qǐng)求參數(shù)進(jìn)行合法性校驗(yàn),過(guò)濾掉明顯不合理的請(qǐng)求,避免這些請(qǐng)求到達(dá)緩存和數(shù)據(jù)庫(kù)層。這種方法特別適合防范惡意攻擊。
實(shí)現(xiàn)示例
@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{userId}") public ResponseEntity<?> getUserById(@PathVariable String userId) { // 1. 基本格式校驗(yàn) if (!userId.matches("\d+")) { return ResponseEntity.badRequest().body("UserId must be numeric"); } // 2. 基本邏輯校驗(yàn) long id = Long.parseLong(userId); if (id <= 0 || id > 100000000) { // 假設(shè)ID范圍限制 return ResponseEntity.badRequest().body("UserId out of valid range"); } // 3. 調(diào)用業(yè)務(wù)服務(wù) User user = userService.getUserById(id); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); } }
在服務(wù)層也可以增加參數(shù)檢驗(yàn):
@Service public class UserServiceImpl implements UserService { // 白名單,只允許查詢(xún)這些ID前綴(舉例) private static final Set<String> ID_PREFIXES = Set.of("100", "200", "300"); @Override public User getUserById(Long userId) { // 更復(fù)雜的業(yè)務(wù)規(guī)則校驗(yàn) String idStr = userId.toString(); boolean valid = false; for (String prefix : ID_PREFIXES) { if (idStr.startsWith(prefix)) { valid = true; break; } } if (!valid) { log.warn("Attempt to access invalid user ID pattern: {}", userId); return null; } // 正常業(yè)務(wù)邏輯... return getUserFromCacheOrDb(userId); } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 實(shí)現(xiàn)簡(jiǎn)單,無(wú)需額外組件
- 能在請(qǐng)求早期攔截明顯不合理的訪問(wèn)
- 可以結(jié)合業(yè)務(wù)規(guī)則進(jìn)行精細(xì)化控制
- 減輕系統(tǒng)整體負(fù)擔(dān)
缺點(diǎn)
- 無(wú)法覆蓋所有非法請(qǐng)求場(chǎng)景
- 需要對(duì)業(yè)務(wù)非常了解,才能設(shè)計(jì)合理的校驗(yàn)規(guī)則
- 可能引入復(fù)雜的業(yè)務(wù)邏輯
- 校驗(yàn)過(guò)于嚴(yán)格可能影響正常用戶體驗(yàn)
策略四:接口限流與熔斷
原理
限流是控制系統(tǒng)訪問(wèn)頻率的有效手段,可以防止突發(fā)流量對(duì)系統(tǒng)造成沖擊。熔斷則是在系統(tǒng)負(fù)載過(guò)高時(shí),暫時(shí)拒絕部分請(qǐng)求以保護(hù)系統(tǒng)。這兩種機(jī)制結(jié)合使用,可以有效防范緩存穿透帶來(lái)的系統(tǒng)風(fēng)險(xiǎn)。
實(shí)現(xiàn)示例
使用SpringBoot+Resilience4j實(shí)現(xiàn)限流和熔斷:
@Configuration public class ResilienceConfig { @Bean public RateLimiterRegistry rateLimiterRegistry() { RateLimiterConfig config = RateLimiterConfig.custom() .limitRefreshPeriod(Duration.ofSeconds(1)) .limitForPeriod(100) // 每秒允許100個(gè)請(qǐng)求 .timeoutDuration(Duration.ofMillis(25)) .build(); return RateLimiterRegistry.of(config); } @Bean public CircuitBreakerRegistry circuitBreakerRegistry() { CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(50) // 50%失敗率觸發(fā)熔斷 .slidingWindowSize(100) // 基于最近100次調(diào)用 .minimumNumberOfCalls(10) // 至少10次調(diào)用才會(huì)觸發(fā)熔斷 .waitDurationInOpenState(Duration.ofSeconds(10)) // 熔斷后等待時(shí)間 .build(); return CircuitBreakerRegistry.of(config); } } @Service public class ProductServiceWithResilience { private final ProductMapper productMapper; private final StringRedisTemplate redisTemplate; private final RateLimiter rateLimiter; private final CircuitBreaker circuitBreaker; public ProductServiceWithResilience( ProductMapper productMapper, StringRedisTemplate redisTemplate, RateLimiterRegistry rateLimiterRegistry, CircuitBreakerRegistry circuitBreakerRegistry) { this.productMapper = productMapper; this.redisTemplate = redisTemplate; this.rateLimiter = rateLimiterRegistry.rateLimiter("productService"); this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("productService"); } public Product getProductById(Long productId) { // 1. 應(yīng)用限流器 return rateLimiter.executeSupplier(() -> { // 2. 應(yīng)用熔斷器 return circuitBreaker.executeSupplier(() -> { return doGetProduct(productId); }); }); } private Product doGetProduct(Long productId) { String cacheKey = "product:" + productId; // 查詢(xún)緩存 String productJson = redisTemplate.opsForValue().get(cacheKey); if (productJson != null) { return JSON.parseObject(productJson, Product.class); } // 查詢(xún)數(shù)據(jù)庫(kù) Product product = productMapper.selectById(productId); // 更新緩存 if (product != null) { redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 1, TimeUnit.HOURS); } else { // 空值緩存,短期有效 redisTemplate.opsForValue().set(cacheKey, "", 5, TimeUnit.MINUTES); } return product; } // 熔斷后的降級(jí)方法 private Product fallbackMethod(Long productId, Throwable t) { log.warn("Circuit breaker triggered for productId: {}", productId, t); // 返回默認(rèn)商品或者從本地緩存獲取 return new Product(productId, "Temporary Unavailable", 0.0); } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 提供系統(tǒng)級(jí)別的保護(hù)
- 能有效應(yīng)對(duì)突發(fā)流量和惡意攻擊
- 保障系統(tǒng)穩(wěn)定性和可用性
- 可以結(jié)合監(jiān)控系統(tǒng)進(jìn)行動(dòng)態(tài)調(diào)整
缺點(diǎn)
- 可能影響正常用戶體驗(yàn)
- 配置調(diào)優(yōu)有一定難度
- 需要完善的降級(jí)策略
- 無(wú)法徹底解決緩存穿透問(wèn)題,只是減輕其影響
策略五:緩存預(yù)熱
原理
緩存預(yù)熱是指在系統(tǒng)啟動(dòng)或特定時(shí)間點(diǎn),提前將可能被查詢(xún)的數(shù)據(jù)加載到緩存中,避免用戶請(qǐng)求時(shí)因緩存不命中而導(dǎo)致的數(shù)據(jù)庫(kù)訪問(wèn)。對(duì)于緩存穿透問(wèn)題,預(yù)熱可以提前將有效數(shù)據(jù)的空間占滿,減少直接查詢(xún)數(shù)據(jù)庫(kù)的可能性。
實(shí)現(xiàn)示例
@Component public class CacheWarmUpTask { @Autowired private ProductMapper productMapper; @Autowired private StringRedisTemplate redisTemplate; @Autowired private RedisBloomFilter bloomFilter; // 系統(tǒng)啟動(dòng)時(shí)執(zhí)行緩存預(yù)熱 @PostConstruct public void warmUpCacheOnStartup() { // 異步執(zhí)行預(yù)熱任務(wù),避免阻塞應(yīng)用啟動(dòng) CompletableFuture.runAsync(this::warmUpHotProducts); } // 每天凌晨2點(diǎn)刷新熱門(mén)商品緩存 @Scheduled(cron = "0 0 2 * * ?") public void scheduledWarmUp() { warmUpHotProducts(); } private void warmUpHotProducts() { log.info("開(kāi)始預(yù)熱商品緩存..."); long startTime = System.currentTimeMillis(); try { // 1. 獲取熱門(mén)商品列表(例如銷(xiāo)量TOP5000) List<Product> hotProducts = productMapper.findHotProducts(5000); // 2. 更新緩存和布隆過(guò)濾器 for (Product product : hotProducts) { String cacheKey = "product:" + product.getId(); redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(product), 6, TimeUnit.HOURS ); // 更新布隆過(guò)濾器 bloomFilter.add("product_filter", product.getId().toString()); } // 3. 同時(shí)預(yù)熱一些必要的聚合信息 List<Category> categories = productMapper.findAllCategories(); for (Category category : categories) { String cacheKey = "category:" + category.getId(); List<Long> productIds = productMapper.findProductIdsByCategory(category.getId()); redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(productIds), 12, TimeUnit.HOURS ); } long duration = System.currentTimeMillis() - startTime; log.info("緩存預(yù)熱完成,耗時(shí):{}ms,預(yù)熱商品數(shù)量:{}", duration, hotProducts.size()); } catch (Exception e) { log.error("緩存預(yù)熱失敗", e); } } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 提高系統(tǒng)啟動(dòng)后的訪問(wèn)性能
- 減少緩存冷啟動(dòng)問(wèn)題
- 可以定時(shí)刷新,保持?jǐn)?shù)據(jù)鮮度
- 避免用戶等待
缺點(diǎn)
- 無(wú)法覆蓋所有可能的數(shù)據(jù)訪問(wèn)
- 占用額外的系統(tǒng)資源
- 對(duì)冷門(mén)數(shù)據(jù)無(wú)效
- 需要合理選擇預(yù)熱數(shù)據(jù)范圍,避免資源浪費(fèi)
策略六:分級(jí)過(guò)濾策略
原理
分級(jí)過(guò)濾策略是將多種防穿透措施組合使用,形成多層防護(hù)網(wǎng)。通過(guò)在不同層次設(shè)置過(guò)濾條件,既能保證系統(tǒng)性能,又能最大限度地防止緩存穿透。一個(gè)典型的分級(jí)過(guò)濾策略包括:前端過(guò)濾 -> API網(wǎng)關(guān)過(guò)濾 -> 應(yīng)用層過(guò)濾 -> 緩存層過(guò)濾 -> 數(shù)據(jù)庫(kù)保護(hù)。
實(shí)現(xiàn)示例
以下是一個(gè)多層防護(hù)的綜合示例:
// 1. 網(wǎng)關(guān)層過(guò)濾(使用Spring Cloud Gateway) @Configuration public class GatewayFilterConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("product_route", r -> r.path("/api/product/**") // 路徑格式驗(yàn)證 .and().predicate(exchange -> { String path = exchange.getRequest().getURI().getPath(); // 檢查product/{id}路徑,確保id為數(shù)字 if (path.matches("/api/product/\d+")) { String id = path.substring(path.lastIndexOf('/') + 1); long productId = Long.parseLong(id); return productId > 0 && productId < 10000000; // 合理范圍檢查 } return true; }) // 限流過(guò)濾 .filters(f -> f.requestRateLimiter() .rateLimiter(RedisRateLimiter.class, c -> c.setReplenishRate(10).setBurstCapacity(20)) .and() .circuitBreaker(c -> c.setName("productCB").setFallbackUri("forward:/fallback")) ) .uri("lb://product-service") ) .build(); } } // 2. 應(yīng)用層過(guò)濾(Resilience4j + Bloom Filter) @Service public class ProductServiceImpl implements ProductService { private final StringRedisTemplate redisTemplate; private final ProductMapper productMapper; private final BloomFilter<String> localBloomFilter; private final RateLimiter rateLimiter; private final CircuitBreaker circuitBreaker; @Value("${cache.product.expire-seconds:3600}") private int cacheExpireSeconds; // 構(gòu)造函數(shù)注入... @PostConstruct public void initLocalFilter() { // 創(chuàng)建本地布隆過(guò)濾器作為二級(jí)保護(hù) localBloomFilter = BloomFilter.create( Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, // 預(yù)期元素?cái)?shù)量 0.001 // 誤判率 ); // 初始化本地布隆過(guò)濾器數(shù)據(jù) List<String> allProductIds = productMapper.getAllProductIdsAsString(); for (String id : allProductIds) { localBloomFilter.put(id); } } @Override public Product getProductById(Long productId) { String productIdStr = productId.toString(); // 1. 本地布隆過(guò)濾器預(yù)檢 if (!localBloomFilter.mightContain(productIdStr)) { log.info("Product filtered by local bloom filter: {}", productId); return null; } // 2. Redis布隆過(guò)濾器二次檢查 Boolean mayExist = redisTemplate.execute( (RedisCallback<Boolean>) connection -> connection.execute( "BF.EXISTS", "product_filter".getBytes(), productIdStr.getBytes() ) != 0 ); if (Boolean.FALSE.equals(mayExist)) { log.info("Product filtered by Redis bloom filter: {}", productId); return null; } // 3. 應(yīng)用限流和熔斷保護(hù) try { return rateLimiter.executeSupplier(() -> circuitBreaker.executeSupplier(() -> { return getProductFromCacheOrDb(productId); }) ); } catch (RequestNotPermitted e) { log.warn("Request rate limited for product: {}", productId); throw new ServiceException("Service is busy, please try again later"); } catch (CallNotPermittedException e) { log.warn("Circuit breaker open for product queries"); throw new ServiceException("Service is temporarily unavailable"); } } private Product getProductFromCacheOrDb(Long productId) { String cacheKey = "product:" + productId; // 4. 查詢(xún)緩存 String cachedValue = redisTemplate.opsForValue().get(cacheKey); if (cachedValue != null) { // 處理空值緩存情況 if (cachedValue.isEmpty()) { return null; } return JSON.parseObject(cachedValue, Product.class); } // 5. 查詢(xún)數(shù)據(jù)庫(kù)(加入DB保護(hù)) Product product = null; try { product = productMapper.selectById(productId); } catch (Exception e) { log.error("Database error when querying product: {}", productId, e); throw new ServiceException("System error, please try again later"); } // 6. 更新緩存(空值也緩存) if (product != null) { redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(product), cacheExpireSeconds, TimeUnit.SECONDS ); // 確保布隆過(guò)濾器包含此ID redisTemplate.execute( (RedisCallback<Boolean>) connection -> connection.execute( "BF.ADD", "product_filter".getBytes(), productId.toString().getBytes() ) != 0 ); localBloomFilter.put(productId.toString()); } else { // 緩存空值,短時(shí)間過(guò)期 redisTemplate.opsForValue().set( cacheKey, "", 60, // 空值短期緩存 TimeUnit.SECONDS ); } return product; } }
優(yōu)缺點(diǎn)分析
優(yōu)點(diǎn)
- 提供全方位的系統(tǒng)保護(hù)
- 各層防護(hù)互為補(bǔ)充,形成完整防線
- 可以靈活配置各層策略
- 最大限度減少資源浪費(fèi)和性能損耗
缺點(diǎn)
- 實(shí)現(xiàn)復(fù)雜度高
- 各層配置需要協(xié)調(diào)一致
- 可能增加系統(tǒng)響應(yīng)時(shí)間
- 維護(hù)成本相對(duì)較高
總結(jié)
防范緩存穿透不僅是技術(shù)問(wèn)題,更是系統(tǒng)設(shè)計(jì)和運(yùn)維的重要環(huán)節(jié)。
在實(shí)際應(yīng)用中,應(yīng)根據(jù)具體業(yè)務(wù)場(chǎng)景和系統(tǒng)規(guī)模選擇合適的策略組合。通常,單一策略難以完全解決問(wèn)題,而組合策略能夠提供更全面的防護(hù)。無(wú)論采用何種策略,定期監(jiān)控和性能評(píng)估都是保障緩存系統(tǒng)高效運(yùn)行的必要手段。
到此這篇關(guān)于Redis預(yù)防緩存穿透的6種策略的文章就介紹到這了,更多相關(guān)Redis緩存穿透內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis的配置、啟動(dòng)、操作和關(guān)閉方法
今天小編就為大家分享一篇Redis的配置、啟動(dòng)、操作和關(guān)閉方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05Redis list 類(lèi)型學(xué)習(xí)筆記與總結(jié)
這篇文章主要介紹了Redis list 類(lèi)型學(xué)習(xí)筆記與總結(jié),本文著重講解了關(guān)于List的一些常用方法,比如lpush 方法、lrange 方法、rpush 方法、linsert 方法、 lset 方法等,需要的朋友可以參考下2015-06-06redis lettuce連接池經(jīng)常出現(xiàn)連接拒絕(Connection refused)問(wèn)題解決
本文主要介紹了在Windows 10/11系統(tǒng)中使用Spring Boot和Lettuce連接池訪問(wèn)Redis時(shí),遇到的連接拒絕問(wèn)題,下面就來(lái)介紹一下解決方法,感興趣的可以了解一下2025-03-03redis.clients.jedis.exceptions.JedisDataException異常的錯(cuò)誤解決
本文主要介紹了redis.clients.jedis.exceptions.JedisDataException異常的錯(cuò)誤解決,這個(gè)異常通常發(fā)生在嘗試連接到一個(gè)?Redis?服務(wù)器時(shí),客戶端發(fā)送了一個(gè)?AUTH?命令來(lái)驗(yàn)證密碼,但是沒(méi)有配置密碼驗(yàn)證,下來(lái)就來(lái)解決一下2024-05-05Redis數(shù)據(jù)庫(kù)分布式設(shè)計(jì)方案介紹
大家好,本篇文章主要講的是Redis數(shù)據(jù)庫(kù)分布式設(shè)計(jì)方案介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01一文搞懂Redis中String數(shù)據(jù)類(lèi)型
string 是 redis 最基本的類(lèi)型,你可以理解成與 Memcached 一模一樣的類(lèi)型,一個(gè) key 對(duì)應(yīng)一個(gè) value。今天通過(guò)本文給大家介紹下Redis中String數(shù)據(jù)類(lèi)型,感興趣的朋友一起看看吧2022-04-04Redis優(yōu)惠券秒殺企業(yè)實(shí)戰(zhàn)
本文主要介紹了Redis優(yōu)惠券秒殺企業(yè)實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵的處理教程
這篇文章主要給大家介紹了關(guān)于redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11