BloomFilter(布隆过滤):将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。在缓存之前在加一层 BloomFilter,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。
redis中没有需要的key,直接穿过redis访问数据库了。
bitmap是一组二进制数组,每个需要存储到redis的key,使用若干个哈希算法计算出若干个数组下标;
比如hello的下标是hash1()->1,hash2()->2,hash3()->3,hash4()->4。那么就会把下标为1,2,3,4的数组元素改为1,
比如hi的下标是2,3,4,5那么也会把2,3,4,5的数组元素改为1。
这样就可以通过相应位置数组值是否为1判断redis中是否存在这个key。
当然也会有误判出现,当不同的key通过若干哈希算法得到数组下标一样时,就会造成误判。
设置布隆过滤器时需要设置
1,需要存储的key数量
2,误差率,因为误差率越小,需要的hash算法的数量也就越多,bitmap的长度也就越长。
pom依赖
<!--布隆过滤器-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.7</version>
</dependency>
配置布隆过滤器
/**
* 配置布隆过滤器
*/
@Configuration
public class BloomFilterConfig {
@Autowired
private RedissonClient redissonClient;
/**
* 布隆过滤器
* 布隆过滤器可以判断是否大概率存在某个key,存在一定的误差
* 取决于设置的错误比例,根据插入数量、允许错误比例设置2进制数组,根据多个hash函数计算数组下标,将对应下标改为1
* 所以就存在不同的key计算出的数组下标相同的情况,那么就会造成误判
* @return
*/
@Bean
public RBloomFilter<String> orderBloomFilter() {
//过滤器名称
String filterName = "myBloomFilter";
// 预期插入数量
long expectedInsertions = 10000L;
// 错误比率
double falseProbability = 0.01;
RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter(filterName);
bloomFilter.tryInit(expectedInsertions, falseProbability);
return bloomFilter;
}
}
设置redis缓存后,将key保存到布隆过滤器中
查询缓存前,先查询布隆过滤器是否大概率存在这个key,如果存在就获取,如果不存在就返回,防止请求直接打到数据库上,防止缓存穿透
@Component
public class RedisTask {
Log log = LogFactory.getLog(RedisTask.class);
private final RedisUtils redisUtils;
@Autowired
public RedisTask(RedisUtils redisUtils) {
this.redisUtils = redisUtils;
}
@Resource
private RBloomFilter<String> myBloomFilter;
@PostConstruct
public void task1() {
RedisValueDO valueDO = new RedisValueDO();
valueDO.setId(1);
valueDO.setStation("910");
valueDO.setTime(new Date());
//1,设置redis缓存
String key = "login:" + UUID.randomUUID();
redisUtils.set(key, valueDO, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
//2,key保存到布隆过滤器
log.info("布隆过滤器中添加key:"+ key);
myBloomFilter.add(key);
//根据布隆过滤器判断key是否可能存在
if(myBloomFilter.contains(key)){
//获取redis值
RedisValueDO value = (RedisValueDO)redisUtils.get(key);
if(!Objects.isNull(value)){
log.info("命中缓存");
}else {
//3、缓存不存在则查询数据库
log.info("未命中缓存,查询数据库");
//重新设置缓存
redisUtils.set(key, valueDO, AuthConstant.EXPIRATION_TIME_IN_SECOND, TimeUnit.SECONDS);
}
}else {
log.info("判定key不存在,不进行查询");
}
}
}