分布式布隆过滤器

佚名 / 2023-08-20 / 原文

1. 分布式布隆过滤器的价值

集群环境太浪费系统资源、集群环境也不容易对布隆过滤器进行维护,所以采用Redisson框架的分布式布隆过滤器。

2. Redisson提供的分布式布隆过滤器的使用

// 获取一个分布式的布隆过滤器(RedissonClient)
RBloomFilter<V> getBloomFilter(String name);

// 初始化分布式的布隆过滤器(RBloomFilter)
boolean tryInit(long expectedInsertions, double falseProbability);

// 判断布隆过滤器中是否存在数据(RBloomFilter)
boolean contains(T object);

// 判断布隆过滤器是否存在(RBloomFilter)
boolean isExists();

3. 分布式布隆过滤器的初始化

@PostConstruct
public void init(){
	// 查询所有的skuId
	List<Long> skuIds = skuInfoMapper.findAllSkuIds();

	// 通过RedissonClient创建BloomFilter
	RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(GmallConstant.REDIS_BLOOMFILTER_SKU_DETAIL);

	// 判断bloomFilter是否存在,如果不存在进行初始化
	if (!bloomFilter.isExists()){
		bloomFilter.tryInit(1000000, 0.000001);

		// 将skuId存储到bloomFilter
		skuIds.forEach(skuId -> {
			bloomFilter.add(skuId);
		});
	}

	log.info("布隆过滤器初始化完毕,判断skuId为51的商品在bloomFilter中是否存在:" + bloomFilter.contains(51L));
}

4. 获取分布式布隆过滤器

private RBloomFilter<Long> redissonBloomFilter;

// 获取分布式布隆过滤器
@PostConstruct
public void getRedissonBloomFilter(){
	log.info("获取了分布式布隆过滤器......");
	redissonBloomFilter = redissonClient.getBloomFilter(GmallConstant.REDIS_BLOOMFILTER_SKU_DETAIL);
}

// 判断当前skuId在布隆过滤器中是否存在
if (!redissonBloomFilter.contains(skuId)){
	log.error("当前查询的商品在布隆过滤器中不存在,在数据库中也不存在......");
	return null;
}

5. 布隆过滤器的重置

5.1 思路

创建新的bloom过滤器,删除原有的bloom过滤器,在对新的bloom过滤器进行重命名;并且需要保证后两者的原子性操作

5.2 代码实现

// 查询所有的skuId
List<Long> skuIds = skuInfoMapper.findAllSkuIds();

// 创建新的布隆过滤器
RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(GmallConstant.REDIS_BLOOMFILTER_SKU_DETAIL_NEW);
bloomFilter.tryInit(1000000, 0.000001);
skuIds.forEach(skuId -> bloomFilter.add(skuId));
log.info("新的布隆过滤器创建好了,判断100在布隆过滤器中是否存在:" + bloomFilter.contains(100L));

// 删除原有的布隆过滤器和配置,并把新创建的布隆过滤器和配置名称重命名为原有的布隆过滤器和配置名称
String script = "redis.call(\"del\" , KEYS[1])\n" +
	"redis.call(\"del\" , \"{\"..KEYS[1]..\"}:config\")\n" +
	"redis.call(\"rename\" , KEYS[2] , KEYS[1])\n" +
	"redis.call(\"rename\" , \"{\"..KEYS[2]..\"}:config\" , \"{\"..KEYS[1]..\"}:config\")\n" +
	"return 1";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(GmallConstant.REDIS_BLOOMFILTER_SKU_DETAIL, GmallConstant.REDIS_BLOOMFILTER_SKU_DETAIL_NEW));

5.3 服务触发时机

  1. 定时任务触发
@Slf4j
@Component
public class ResetBloomFilterTimeTask {

    @Autowired
    private BloomFilterService bloomFilterService;

    /**
     * ?:表示废弃掉这一位,仅支持日期和星期,这两位其中之一被指定值以后,为了避免冲突,需要将另一个域的值设为?
     * cron表达式各位的意义:秒 分 时 日期 月 星期,不支持年
     */
    @Scheduled(cron = "0 0 3 */7 * ?") // cron属性需要一个cron表达式,这个cron表达式用来指定方法在执行的时候的时间规则
    public void resetBloomFilterTask(){
        bloomFilterService.resetBloomFilter();
        log.info("布隆过滤器重置方法执行了......");
    }
}

启动类添加@EnableScheduling注解,开启定时任务功能
cron表达式:https://help.aliyun.com/document_detail/64769.html
2. 提供一个接口,在后台开放调用该接口的功能,系统前台提供一个按钮,点击按钮调用该接口

/**
 * 重置布隆过滤器的接口
 */
@RestController
@RequestMapping(value = "/admin/product")
public class BloomFilterController {

    @Autowired
    private BloomFilterService bloomFilterService;

    @GetMapping(value = "/resetBloomFilter")
    public Result resetBloomFilter(){
        bloomFilterService.resetBloomFilter();
        return Result.ok();
    }
}
  1. 每删除一次数据调用一次