布隆过滤器预防缓存穿透

缓存穿透常用解决方法:

  1. 缓存空对象——当数据库没有查到的时候,也向redis中插入null的<key,value>
    • 优点:实现简单,维护方便
    • 缺点:额外的内存消耗,可能造成短期的不一致
  2. 布隆过滤
    • 优点:内存占用啥哦,没有多余的key
    • 缺点:实现复杂,可能存在误判

1. 布隆过滤器配置

@Configuration
public class BloomFilterConfig {
    @Bean
    public BloomFilter<Integer> bloomFilter() {
        // 初始化布隆过滤器,预计插入100000个元素,误报率为0.01
        return BloomFilter.create(Funnels.integerFunnel(), 100000, 0.01);
    }
}

2. 防止缓存穿透

controller层

@GetMapping("/data/{key}")
public AjaxResult getData(@PathVariable Integer key) {
    return getDateService.getDateFromCache(key);
}

service层

//如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库里一定会存在这个数据
@Override
public AjaxResult getDateFromCache(Integer key) {

    // 检查布隆过滤器中是否存在该key,存在则放行
    if (!bloomFilter.mightContain(key)) {
        log.info("Key {} does not exist in bloom filter", key);
        return AjaxResult.error("获取数据失败"); // 布隆过滤器判定该key不存在
    }

    // 从Redis缓存中获取数据
    log.info("Retrieving data for key {} from Redis", key);
    Object data = redisTemplate.opsForValue().get("Document:" + key.toString());

    //redis 中也没有得到数据,说明在数据库中
    if(data == null){
        log.info("Data not found in Redis for key {}. Fetching from DB...", key);
        data = documentMapper.selectById(key);

        //从数据库取出来后,放入redis和布隆中

        // 将key添加到布隆过滤器
        bloomFilter.put(key);
        log.info("Added key {} to bloom filter", key);

        // 将数据存入Redis缓存
        redisTemplate.opsForValue().set("Document:"+ key, data);
        log.info("Data {} stored in Redis", data);
    }
    return AjaxResult.success(data);
}

3. 数据初始化

将数据的id加入到布隆过滤器中,同时将要缓存的数据加入到redis中

@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行

@Component
@RequiredArgsConstructor
public class DataInitializer {

    private final BloomFilter<Integer> bloomFilter;

    private final RedisTemplate<String, Object> redisTemplate;

    private final IDocumentService documentService;

    @PostConstruct
    public void init() {
        List<Document> documents = documentService.list(null);

        for (Document document : documents) {
            // 将数据加入布隆过滤器
            bloomFilter.put(document.getId());

            // 将数据加入Redis
            String key = "Document:" + document.getId();
            redisTemplate.opsForValue().set(key, document);
        }
    }
}
image-20240628135733630

4. 测试

1. 已经存储在redis中

image-20240628133903696

image-20240628133849080

2. key不存在布隆过滤器中

image-20240628134040722

image-20240628134105237

3. key在布隆过滤器中,但是redis中数据过期

image-20240628134609361

image-20240628134540656

5. 代码仓库

https://gitee.com/ucascyl/bloom-filter