• SpringBoot使用guava的布隆过滤器


    介绍

            布隆过滤器(BloomFilter)是一种数据结构,特点是高效地插入和查询,可以用来告诉你某个key一定不存在或者可能存在,它是用多个哈希函数,将一个key映射到多个数组下标中。

    特点

            布隆过滤器可以告诉我们 "某样东西一定不存在或者可能存在",也就是说布隆过滤器说这个key不存在则一定不存在,布隆过滤器说这个key存在则可能不存在(会存在误判),利用这个判断是否存在的特点可以做很多有趣的事情。

    数据结构

            由一串很长的二进制数组向量组成,可以将其看成一个二进制数组。既然是二进制,那么里面存放的不是0,就是1,但是初始默认值都是0。

    新增数据

             把一个key经过多个hash函数计算出多个数组下标,把计算出的下标的位置的0变成1。

    查询数据

            当要查询一个key是否在布隆过滤器中,用相同的方法计算出多个1,判断根据这个key计算出的多个1的位置在布隆过滤器中是否都是1,如果都是那么代表可能存在,如果不都是代表一定不存在。

    删除数据

            布隆过滤器很难进行删除操作。

    作用

            高效判断一个数据在不在某一组数据中。

    误差率

            创建布隆过滤器时,可以设置误差率,误差率越小,hash函数越多,二进制数据占用空间越大。hash函数越多,计算出的hash值多,对应计算出的二进制数组下标越多,效率越低,有一个位置在布隆过滤器中置不为1,就代表不存在,精确度越高。

    业务使用

            布隆过滤器就是在Redis缓存前面在加一层过滤,负责过滤无效的请求,如果请求没有通过布隆过滤器就不会到缓存和数据库。

    布隆过滤器的优点

    • 空间效率 和 查询效率都远远超过一般的算法。
      • 增加和查询元素的时间复杂为O(N)(N为哈希函数的个数,通常情况比较小)。
      • 占用空间小,如果允许存在一定的误判,布隆过滤器是非常节省空间。
    • 保密性强,布隆过滤器不存储数据本身。

    布隆过滤器的缺点

    • 有一定的误判率,但是可以通过调整参数来降低。
    • 无法获取元素本身。
    • 很难删除元素。

    快速开始

    导入依赖

    1. <dependency>
    2. <groupId>com.google.guavagroupId>
    3. <artifactId>guavaartifactId>
    4. <version>28.0-jreversion>
    5. dependency>

    配置类

    把布隆过滤器对象注入进IOC容器。

    1. import com.google.common.hash.BloomFilter;
    2. import com.google.common.hash.Funnels;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import java.nio.charset.Charset;
    6. @Configuration
    7. public class BloomFilterConfig {
    8. /**
    9. * expectedInsertions:期望添加的数据个数
    10. * fpp:期望的误判率,期望的误判率越低,布隆过滤器计算时间越长
    11. * @return
    12. */
    13. @Bean
    14. public BloomFilter goodsIDBloom(){
    15. BloomFilter filter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 1000,0.00001);
    16. return filter;
    17. }
    18. @Bean
    19. public BloomFilter orderBloom(){
    20. BloomFilter filter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("utf-8")), 1000,0.00001);
    21. return filter;
    22. }
    23. }

    启动项目时把所有key插入布隆过滤器

    1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    2. import com.google.common.hash.BloomFilter;
    3. import com.liu.seckill.entity.SeckillGood;
    4. import com.liu.seckill.service.SeckillGoodService;
    5. import com.liu.seckill.service.UserService;
    6. import lombok.extern.slf4j.Slf4j;
    7. import org.springframework.beans.factory.InitializingBean;
    8. import org.springframework.beans.factory.annotation.Autowired;
    9. import org.springframework.beans.factory.annotation.Qualifier;
    10. import org.springframework.context.annotation.Configuration;
    11. import org.springframework.data.redis.core.RedisTemplate;
    12. import org.springframework.scheduling.annotation.Scheduled;
    13. import java.time.Duration;
    14. import java.time.LocalDateTime;
    15. import java.util.ArrayList;
    16. import java.util.HashMap;
    17. import java.util.List;
    18. import java.util.Map;
    19. import java.util.concurrent.TimeUnit;
    20. @Slf4j
    21. @Configuration
    22. public class InitConfig implements InitializingBean {
    23. @Autowired
    24. UserService userService;
    25. @Autowired
    26. SeckillGoodService seckillGoodService;
    27. @Autowired
    28. RedisTemplate redisTemplate;
    29. @Autowired
    30. RedisConfig redisConfig;
    31. @Autowired
    32. @Qualifier("goodsIDBloom")
    33. BloomFilter goodsIDBloom;
    34. /**
    35. * 把商品库存加载到 Redis中
    36. * 每天更新
    37. * @throws Exception
    38. */
    39. @Override
    40. @Scheduled(fixedRate = 1000*60*60*24)
    41. public void afterPropertiesSet() throws Exception {
    42. //只缓存秒杀还没结束或秒杀还没开始的商品
    43. List seckillGoods = seckillGoodService.list(new QueryWrapper().ge("end_time", LocalDateTime.now()));
    44. if (seckillGoods == null) {
    45. log.info("暂无秒杀商品");
    46. return;
    47. }
    48. //将秒杀商品ID和库存分别存入redis中
    49. List seckillGoodIDList = new ArrayList<>();
    50. for (SeckillGood seckillGood : seckillGoods) {
    51. seckillGoodIDList.add(seckillGood.getSgId());
    52. //设置过期时间
    53. long millis = Duration.between(LocalDateTime.now(), seckillGood.getEndTime()).toMillis();
    54. redisTemplate.opsForValue().set("seckillGoodID:" + seckillGood.getSgId(), seckillGood.getSgStock(), millis, TimeUnit.MILLISECONDS);
    55. //存储值到布隆过滤器中
    56. goodsIDBloom.put(seckillGood.getSgId()+"");
    57. }
    58. redisTemplate.opsForValue().set("seckillGoodIDList:", seckillGoodIDList,1,TimeUnit.DAYS);
    59. }
    60. }

    使用布隆过滤器

    查询redis之前先走布隆过滤器,如果布隆过滤器判断没有则一定没有。

    1. @RestController
    2. @Slf4j
    3. @RequestMapping("/seckillOrder")
    4. public class SeckillOrderController {
    5. //如果有多个布隆过滤器,就同时使用@Qualifier和@Autowired
    6. @Autowired
    7. @Qualifier("goodsIDBloom")
    8. BloomFilter goodsIDBloom;
    9. @Autowired
    10. @Qualifier("orderBloom")
    11. BloomFilter orderBloom;
    12. @GetMapping("/seckillGoods/{goodId}")
    13. public Result seckillGoods(@PathVariable("goodId") Long goodId) {
    14. ValueOperations valueOperations = redisTemplate.opsForValue();
    15. //判断秒杀商品是否存在
    16. //如果商品id在布隆过滤器中存在,那么就要再去判断在不在redis中,在,才能证明真的在
    17. if (goodsIDBloom.mightContain(goodId+"")){
    18. ArrayList seckillGoodIDList = (ArrayList) valueOperations.get("seckillGoodIDList:");
    19. Assert.isTrue(seckillGoodIDList.contains(goodId),"未找到该商品,商品ID有误或此商品不参与秒杀或此商品秒杀已结束");
    20. }else {
    21. Assert.isTrue(false,"未找到该商品,商品ID有误或此商品不参与秒杀或此商品秒杀已结束");
    22. }
    23. }
    24. }
  • 相关阅读:
    【字节跳动技术团队】2020年-2022年精选文章后端篇
    windows修改默认端口3389
    【#HDC2022】HarmonyOS体验官活动正式开启,赶快投稿赢限量奖品吧!
    电子工程领域老牌期刊IET Radar, Sonar & Navigation发展历史(始于1872年)
    Elasticsearch深入理解(十五)——版本冲突问题解决方案
    【Mycat2实战】四、Mycat实现分库分表【概念篇】
    java基础巩固-宇宙第一AiYWM:为了维持生计,做项目经验之~SSM项目错误集锦Part5(页面好卡呀、反应好慢呀)~整起
    idea创建vue项目,引用elementUI并从springboot项目获取数据1
    【导航】嵌入式 Linux 学习专栏目录 【快速跳转】
    Python--traceback模块的基本用法(异常处理)
  • 原文地址:https://blog.csdn.net/m0_56750901/article/details/126651748