欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis預(yù)防緩存穿透的6種策略

 更新時(shí)間:2025年04月28日 08:14:08   作者:風(fēng)象南  
緩存穿透是指查詢(xún)一個(gè)根本不存在的數(shù)據(jù),由于緩存不命中,請(qǐng)求會(huì)穿透緩存層直接訪問(wèn)數(shù)據(jù)庫(kù),本文整理了6個(gè)Redis預(yù)防緩存穿透的方法,希望對(duì)大家有一定的幫助

在高并發(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)閉方法

    今天小編就為大家分享一篇Redis的配置、啟動(dòng)、操作和關(guān)閉方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • 使用Grafana監(jiān)控Redis的操作方法

    使用Grafana監(jiān)控Redis的操作方法

    這篇文章主要介紹了使用Grafana監(jiān)控Redis,號(hào)稱(chēng)下一代可視化監(jiān)控系統(tǒng),結(jié)合SpringBoot使用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • Redis list 類(lèi)型學(xué)習(xí)筆記與總結(jié)

    Redis list 類(lèi)型學(xué)習(xí)筆記與總結(jié)

    這篇文章主要介紹了Redis list 類(lèi)型學(xué)習(xí)筆記與總結(jié),本文著重講解了關(guān)于List的一些常用方法,比如lpush 方法、lrange 方法、rpush 方法、linsert 方法、 lset 方法等,需要的朋友可以參考下
    2015-06-06
  • redis lettuce連接池經(jīng)常出現(xiàn)連接拒絕(Connection refused)問(wèn)題解決

    redis 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-03
  • redis.clients.jedis.exceptions.JedisDataException異常的錯(cuò)誤解決

    redis.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-05
  • Redis數(shù)據(jù)庫(kù)分布式設(shè)計(jì)方案介紹

    Redis數(shù)據(jù)庫(kù)分布式設(shè)計(jì)方案介紹

    大家好,本篇文章主要講的是Redis數(shù)據(jù)庫(kù)分布式設(shè)計(jì)方案介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-01-01
  • 一文搞懂Redis中String數(shù)據(jù)類(lèi)型

    一文搞懂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-04
  • Redis優(yōu)惠券秒殺企業(yè)實(shí)戰(zhàn)

    Redis優(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-07
  • redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵的處理教程

    redis學(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
  • 基于Redis實(shí)現(xiàn)基本搶紅包算法詳解

    基于Redis實(shí)現(xiàn)基本搶紅包算法詳解

    [key, value]的緩存數(shù)據(jù)庫(kù), Redis官方性能描述非常高, 所以面對(duì)高并發(fā)場(chǎng)景, 使用Redis來(lái)克服高并發(fā)壓力是一個(gè)不錯(cuò)的手段, 本文主要基于Redis來(lái)實(shí)現(xiàn)基本的搶紅包系統(tǒng)設(shè)計(jì),感興趣的朋友跟隨小編一起看看吧
    2024-04-04

最新評(píng)論