布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
Hash面临的问题就是冲突。假设Hash函数是良好的,如果我们的位阵列长度为m个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳m / 100个元素。显然这就不叫空间效率了(Space-efficient)了。解决方法也简单,就是使用多个Hash,如果它们有一个说元素不在集合中,那肯定就不在;如果它们都说在,也有可能性是不存在的。
布隆过滤器的优点:
• 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)
• 保密性强,布隆过滤器不存储元素本身
• 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)
布隆过滤器的缺点:
• 有点一定的误判率,但是可以通过调整参数来降低
• 无法获取元素本身
• 很难删除元素
在我们项目中使用布隆过滤器主要是为了解决Redis缓存穿透问题
org.redisson
redisson
3.17.6
/**
* 容器启动成功以后,连上数据库,查到所有商品id。在布隆里面进行占位
*/
@Slf4j
@Service
public class SkuIdBloomInitService {
@Autowired
SkuInfoService skuInfoService;
@Autowired
RedissonClient redissonClient;
//TODO 布隆只能增,不能删除商品,如果真的数据库删除了商品,需要定期布隆重建。
//【重建】:按钮触发
/**
* 项目一启动就运行
*/
@PostConstruct //当前组件对象创建成功以后执行
public void initSkuBloom(){
log.info("布隆初始化正在进行....");
//1、查询出所有的skuId
List skuIds = skuInfoService.findAllSkuId();
//2、把所有的id初始化到布隆过滤器中
RBloomFilter
/**
*
* 切面点表达式怎么写?
* execution(* com.atguigu.gmall.item.**.*(..))
* @param skuId
* @return
*/
public SkuDetailTo getSkuDetailWithCache(Long skuId) {
String cacheKey = SysRedisConst.SKU_INFO_PREFIX +skuId;
//1、先查缓存
SkuDetailTo cacheData = cacheOpsService.getCacheData(cacheKey,SkuDetailTo.class);
//2、判断
if(cacheData == null){
//3、缓存没有
//4、先问布隆,是否有这个商品
boolean contain = cacheOpsService.bloomContains(skuId);
if(!contain){
//5、布隆说没有,一定没有
log.info("[{}]商品 - 布隆判定没有,检测到隐藏的攻击风险....",skuId);
return null;
}
//6、布隆说有,有可能有,就需要回源查数据
boolean lock = cacheOpsService.tryLock(skuId); //为当前商品加自己的分布式锁。100w的49号查询只会放进一个
if(lock){
//7、获取锁成功,查询远程
log.info("[{}]商品 缓存未命中,布隆说有,准备回源.....",skuId);
SkuDetailTo fromRpc = getSkuDetailFromRpc(skuId);
//8、数据放缓存
cacheOpsService.saveData(cacheKey,fromRpc);
//9、解锁
cacheOpsService.unlock(skuId);
}
//9、没获取到锁
try {Thread.sleep(1000);
return cacheOpsService.getCacheData(cacheKey,SkuDetailTo.class);
} catch (InterruptedException e) {
}
}
//4、缓存中有
return cacheData;
}
@Override
public void rebuildBloom(String bloomName, BloomDataQueryService dataQueryService) {
RBloomFilter
@Scheduled(cron = "0 0 3 ? * 3")
public void rebuild(){
// System.out.println("xxxxxx");
bloomOpsService.rebuildBloom(SysRedisConst.BLOOM_SKUID,bloomDataQueryService);
}