什么是缓存穿透
我们在实际开发中使用redis时会存在很多生产问题,例如我们今天要讲的缓存穿透问题
缓存穿透问题主要是由于请求的数据在缓存和数据库中都不存在,此时当系统出现恶意攻击,大量请求访问缓存和数据库都不存在的数据,所有的请求将会到达数据库从而对数据库造成的攻击导致数据库宕机。
相信大家也看过网上很多解决方法例如:
- 缓存和数据库都不存在时直接在 redis 中缓存空对象并设置过期时间;
- 当缓存中不存在时使用分布式锁保证同时只有一个线程查询数据库;
- 使用Redis Set数据类型存储所有已存在数据每次提前判断;
但以上的方式或多或少都存在一些问题:
- 直接缓存空对象会出现数据一致性的问题;
- 如果在系统使用高峰期只有一个线程访问数据库,这可能会导致大量的请求缓慢或超时;
- 直接将所有数据存入Redis Set结构中会导致占用大量的内存资源,并且数据过多会导致大key问题;
我们今天主要来讲一下使用 RBloomFilter 实现的布隆过滤器实现缓存穿透问题
布隆过滤器简介
布隆过滤器是一个由多位0/1组成的位图结构(二进制数组),初始化每个位置都为0,会将存入的数据进行三次hash计算再映射到不同的位置,并将映射后的座位如果是0则改为1
查询时则通过相同的hash算法得到映射的位置并判断映射的位置的值,若存在一个及以上的位置为0则代表该数据不存在布隆过滤器中,若映射的位置全部都为1则代表该数据可能在布隆过滤器中,可能不在存在误判的可能性。
根据布隆过滤器的特性我们可以得到其存在的问题有
- 对于映射的位置的判断可能存在误判
- 存入布隆过滤器的数据不方便删除,删除可能会导致其他大量数据的误判
解决误判的方法就是尽可能增加布隆过滤器的长度,减少哈希碰撞,当然内存的占用也就会相应提高,这里就存在了一个空间与性能的抉择问题
而解决布隆过滤器不能删除数据的问题我们使用了 redis 的 set 结构在判断完布隆过滤器后再加一层是否为已删除的数据判断;
将业务中需要删除的数据提前存入 set 中,在使用了布隆过滤器判断后如果该数据存在则再进行是否存在 set 判断
整个判断流程图如下:
我们使用 Redisson 的 RBloomFilter 实现布隆过滤器
RBloomFilter 中可以设置布隆过滤器位数组大小,布隆过滤器容错率,数据预计插入量等属性
我们可以使用@Bean注解将其注入到Spring IOC容器中
/**
* 布隆过滤器
*/
@Bean
public RBloomFilter<String> cachePenetrationBloomFilter(RedissonClient redissonClient, BloomFilterPenetrateProperties bloomFilterPenetrateProperties) {
RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter(bloomFilterPenetrateProperties.getName());
cachePenetrationBloomFilter.tryInit(bloomFilterPenetrateProperties.getExpectedInsertions(), bloomFilterPenetrateProperties.getFalseProbability()); //预期插入量、容错率推荐0.05
return cachePenetrationBloomFilter;
}
具体使用:
RBloomFilter<String> bloomFilter; //注入对象
public void main() {
boolean hasName = bloomFilter.contains(name); //判断name是否存在布隆过滤器
bloomFilter.add(name); //将name加入布隆过滤器中
}
总结:
- 通过布隆过滤器的位数组可有效解决缓存穿透问题
- 相当于在缓存和数据库前提前判断而防止大量请求打到数据库
- 布隆过滤器存在哈希碰撞导致误判和不能删除
- 使用增大容量可减少哈希碰撞导致的误判
- Redis Set可有效解决不能删除问题