高性能缓存实践-解决缓存穿透-基于内存布隆过滤器

本文介绍了布隆过滤器的概念及其在解决缓存穿透问题中的应用。通过使用Google Guava库实现了一个基于SpringBoot Cache的布隆过滤器示例,用于在查询数据时判断缓存key是否存在,减少无效数据库查询。实践步骤包括创建布隆过滤器、存储缓存key、判断key是否存在,并提供了手动初始化布隆过滤器的接口。

目标

布隆过滤器

参考:百度百科-布隆过滤器

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

应用

网页URL的去重,垃圾邮件的判别,集合重复元素的判别,查询加速(比如基于key-value的存储系统)等。

我的理解

布隆过滤器用于鉴别一个元素是否一定不存在或者可能存在。占用内存资源很少,并且查询效率高效。

计算布隆过滤器的容量可以计算,Bloom Filter Calculator

可以看到存入100万,误差在0.001,需要的size 大小在 1775.07KB ,只有1.73M。要求的误差越小,对cpu和内存的消耗越高。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tNGhSmPN-1601430760248)(https://s1.ax1x.com/2020/06/24/Nan5gH.png)]

实践

基于 spring boot cache 和 goole guava 包

项目地址:https://github.com/gengzi/codecopy

具体代码:fun.gengzi.codecopy.business.product 下的代码

思路:

两个方法:

一个是将所有缓存的key加载到内存的布隆过滤器,供查询数据时使用

一个是鉴别布隆过滤器是否包含该缓存的key,不包含,直接响应,包含,查询数据库返回数据。

引入 goole guava 依赖

guava 提供了布隆过滤器

   <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>

业务类

fun.gengzi.codecopy.business.product.service.impl.ProductCacheServiceImpl

关于 BloomFilter 的用法,可以查看下api

 
    // 存放的数量
    private static int size = 1000000;
    // 创建布隆过滤器
    private static final BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
 
    // 将缓存的key,设置到布隆过滤器中
    @Override
    public void putBloomKey() {
        List<Integer> allId = productDao.getAllId();
        allId.forEach(id -> {
            bloomFilter.put(id);
        });
    }
 
        /**
     * 根据产品id 获取产品信息
     * <p>
     * 使用布隆过滤器,判断id 是否存布隆过滤器中 存在
     * 查缓存,如果有,返回
     * 无,直接返回
     *
     * @param id
     * @return
     */
    @Cacheable(cacheManager = "loclRedisCacheManagers", value = "PRODUCT_INFO_ID_NOPREFIX", key = "#id", cacheNames = {"PRODUCT_INFO_ID_NOPREFIX"})
    @Override
    public Product getOneProductCacheInfoBloom(Integer id) {
              // 如果存在返回 true ,不存在返回 false
        boolean isContain = bloomFilter.mightContain(id);
        if (isContain) {
            return productDao.findById(id.longValue()).orElseThrow(() -> new RrException("error ", RspCodeEnum.FAILURE.getCode()));
        } else {
            throw new RrException("error ", RspCodeEnum.FAILURE.getCode());
        }
    }

controller 层

在本例中,需要手动的执行,将key存入到布隆过滤器中,实践中,可以考虑在上线后,在运维页面,手动初始化一下布隆过滤器数据。

    @ApiOperation(value = "将key存入布隆过滤器", notes = "将key存入布隆过滤器")
    @ApiResponses({@ApiResponse(code = 200, message = "\t{\n" +
            "\t    \"status\": 200,\n" +
            "\t    \"info\": {\n" +
            "\t        }\n" +
            "\t    \"message\": \"信息\",\n" +
            "\t}\n")})
    @PostMapping("/putBloomKey")
    @ResponseBody
    public ReturnData putBloomKey() {
        productCacheService.putBloomKey();
        ReturnData ret = ReturnData.newInstance();
        ret.setSuccess();
        return ret;
    }
 
        @ApiOperation(value = "缓存穿透-使用布隆过滤器(Redis)", notes = "缓存穿透-使用布隆过滤器(Redis)")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "id", required = true)})
    @ApiResponses({@ApiResponse(code = 200, message = "\t{\n" +
            "\t    \"status\": 200,\n" +
            "\t    \"info\": {\n" +
            "\t        }\n" +
            "\t    \"message\": \"信息\",\n" +
            "\t}\n")})
    @PostMapping("/findCacheUseRedisBloomByErrorId")
    @ResponseBody
    public ReturnData findCacheUseRedisBloomByErrorId(@RequestParam("id") Integer id) {
        Product oneProductCacheInfo = productCacheService.getOneProductCacheInfoRedisBloom(id);
        ReturnData ret = ReturnData.newInstance();
        ret.setSuccess();
        ret.setMessage(oneProductCacheInfo);
        return ret;
    }
 

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

耿子666

谢谢大爷!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值