布隆过滤器介绍及实战应用(防止缓存穿透)

布隆过滤器介绍

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

Hash面临的问题就是冲突。假设Hash函数是良好的,如果我们的位阵列长度为m个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳m / 100个元素。显然这就不叫空间效率了(Space-efficient)了。解决方法也简单,就是使用多个Hash,如果它们有一个说元素不在集合中,那肯定就不在;如果它们都说在,也有可能性是不存在的。

布隆过滤器的优点:

• 时间复杂度低,增加和查询元素的时间复杂为O(N),(N为哈希函数的个数,通常情况比较小)

• 保密性强,布隆过滤器不存储元素本身

• 存储空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间的(相比其他数据结构如Set集合)

布隆过滤器的缺点:

• 有点一定的误判率,但是可以通过调整参数来降低

• 无法获取元素本身

• 很难删除元素

在我们项目中使用布隆过滤器主要是为了解决Redis缓存穿透问题

布隆过滤器介绍及实战应用(防止缓存穿透)_第1张图片

基本使用

引入依赖

        
            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 filter =
                redissonClient.getBloomFilter(SysRedisConst.BLOOM_SKUID);

        //3、初始化布隆过滤器
        //long expectedInsertions, 期望插入的数据量
        //double falseProbability  误判率
        boolean exists = filter.isExists();
        if(!exists){
            //尝试初始化。如果布隆过滤器没有初始化过,就尝试初始化
            filter.tryInit(5000000,0.00001);
        }
        //4、把所有的商品添加到布隆中。 不害怕某个微服务把这个事情做失败
        for (Long skuId : skuIds) {
            filter.add(skuId);
        }
        log.info("布隆初始化完成....,总计添加了 {} 条数据",skuIds.size());
    }

详情业务(布隆过滤器使用)

    /**
     *
     * 切面点表达式怎么写?
     *  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 oldbloomFilter = redissonClient.getBloomFilter(bloomName);

        //1、先准备一个新的布隆过滤器。所有东西都初始化好
        String newBloomName = bloomName + "_new";
        RBloomFilter bloomFilter = redissonClient.getBloomFilter(newBloomName);
        //2、拿到所有商品id
//        List allSkuId = skuInfoService.findAllSkuId();
        List list = dataQueryService.queryData(); //动态决定
        //3、初始化新的布隆
        bloomFilter.tryInit(5000000,0.00001);

        for (Object skuId : list) {
            bloomFilter.add(skuId);
        }

        //5、两个交换;nb 要变成 ob。 大数据量的删除会导致redis卡死
        //最极致的做法:lua。 自己尝试写一下这lua脚本
        oldbloomFilter.rename("bbbb_bloom"); //老布隆下线
        bloomFilter.rename(bloomName); //新布隆上线

        //6、删除老布隆,和中间交换层
        oldbloomFilter.deleteAsync();
        redissonClient.getBloomFilter("bbbb_bloom").deleteAsync();
    }

测试

    @Scheduled(cron = "0 0 3 ? * 3")
    public void rebuild(){
//        System.out.println("xxxxxx");
        bloomOpsService.rebuildBloom(SysRedisConst.BLOOM_SKUID,bloomDataQueryService);
    }

你可能感兴趣的:(java,服务器,开发语言,缓存)