BloomFilter:布隆过滤器和Redis缓存穿透

概念

BloomFilter,中文名叫布隆过滤器,用于判断目标值是否存在。

例子

设一个队伍有10人。需要判断张三是否在其中。
常规的思路就是遍历这10人。然而随着人数规模的增加,遍历所需要的时间会越来越长。
因此考虑这样一个方案:维护一个辅助list,其元素值为0或1,表示该人是否在队伍中。索引使用每个人姓名的笔画数。例如张三,姓名笔画数是10画,那么list[10]就设为1。这样,当我们需要判断张三是否在队伍中时,先算出笔画数10,然后看list[10]是否为1。若list[10]为0,则说明张三一定不在队伍里,就不需要遍历队伍了。
然而这方案有个很明显的特点:list[10]为1,并不能说明张三一定在,因为有跟张三姓名笔画相同的人;但list[10]为0,说明张三一定不在。于是,当list[10]为1时,就去遍历一下队伍,看张三是否在。
这也意味着,如果张三要从队伍中去掉,则需要遍历整个队伍看是否还有其他人的姓名笔画也是10。当确定没有这样的人时,才能将list[10]由1改为0。
可以很明显地看出,相比于直接遍历的方案,该方案需要一个额外的辅助list空间。

原理

如上,就是BloomFilter的流程。
关于辅助list,BloomFilter定义了一个位列阵,其元素值是一个二进制,仅为0或1。默认为0。
BloomFilter是一个典型的用空间换时间的算法。

存储

关于辅助list空间的索引,为了尽可能减少冲突带来的影响,BloomFilter使用了三次hash。也就是一个数据,对其进行三次不同的hash,分别得到三个值k、l、m。然后使用这三个值作为位列阵的索引值,将对应元素值设为1。

查找

当需要判断某个数据是否存在时,对该数据进行三次hash,分别得到k、l、m。然后使用这三个值作为位列阵的索引值,查询对应的元素值。
若三个元素值都为1,则说明该元素可能是存在的;但若至少有一个元素值为0,则说明该元素一定是不存在的。
对于一个元素,若其三个位列阵的点位值都是1,但该元素实际却并不存在,则称之为误判。

删除

就像上述例子中说的,若要删除某个元素,则需对所有元素进行遍历才能确定位列阵中该元素对应的三个值是否可改为0。因此删除困难。一般来说,只添加,不删除。

特点

对于一个数据,若其通过了BloomFilter判定,则不一定在数据库中。但若未通过,则一定不在。
可以看出,BloomFilter有如下优点:

  • 插入/查询时间复杂度是O(1),非常快。
  • 并不存储数据本身,保密性好。
  • 可对所有数据实现映射。

对应地,BloomFilter有如下缺点:

  • 随着元素增多,误算率也会增加。
  • 只能添加元素,不能删除元素。

Redis中BloomFilter的应用

Redis对BloomFilter进行了应用。其实现方式有两种:使用Redis的bitmap实现;使用布隆过滤器插件实现。
根据原理可以知道,BloomFilter需要设置一个初始大小,用于表示预计放入的元素数量。可以想象,该值若过小,则hash冲突的元素就会增多,从而导致误判率的提升。因此该值应设置较大一些。

缓存穿透

对一个数据访问,先判断是否在Redis中,若是则返回;若否则查询数据库,并将查到的值设置到Redis中。这样下次查询就可以顺利查到了。
但若一个数据访问,既不在Redis中也不在数据库中,那么该访问将先访问Redis再访问数据库。若再次访问该数据,则该流程会再次重复。
于是,攻击黑客就发送大量针对不存在数据的请求,那么上述访问流程就会反复重复,导致数据库承担大量压力。在该流程中缓存这道墙形同虚设,称为缓存穿透。
BloomFilter就可以很好地解决该问题:一个针对虚假数据的访问,Redis借助BloomFilter可以很明确地判断出该数据一定不在数据库中,于是就直接返回,不再查询数据库。

缓存穿透和缓存击穿

注意区别二者的概念:

  • 缓存穿透:Redis和数据库中都没有目标数据,每次都要先查Redis再查数据库。
  • 缓存击穿:Redis和数据库中存在目标数据,但Redis的数据过期了。此时有大量并发请求访问该数据,导致这些请求全部发送到数据库上,为数据库带来巨大压力。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Sure!以下是一个示例代码,展示了如何在Java Spring Boot中使用布隆过滤器Redis来解决缓存穿透问题: 首先,你需要在pom.xml文件中添加相应的依赖: ```xml <dependencies> <!-- Spring Boot Starter Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Guava Bloom Filter --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1-jre</version> </dependency> </dependencies> ``` 接下来,创建一个布隆过滤器的工具类 BloomFilterUtil.java: ```java import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @Component public class BloomFilterUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; private BloomFilter<String> bloomFilter; // 设置布隆过滤器的预计插入数据量和误判率 private static final int EXPECTED_INSERTIONS = 1000000; private static final double FPP = 0.001; @PostConstruct public void init() { // 创建布隆过滤器,并将其保存到RedisbloomFilter = BloomFilter.create(Funnels.stringFunnel(), EXPECTED_INSERTIONS, FPP); redisTemplate.opsForValue().set("bloomFilter", bloomFilter); } public boolean mightContain(String key) { // 从Redis中获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 使用布隆过滤器判断key是否可能存在 return bloomFilter.mightContain(key); } public void put(String key) { // 从Redis中获取布隆过滤器 bloomFilter = (BloomFilter<String>) redisTemplate.opsForValue().get("bloomFilter"); // 将key添加到布隆过滤器bloomFilter.put(key); // 将更新后的布隆过滤器保存到RedisredisTemplate.opsForValue().set("bloomFilter", bloomFilter); } } ``` 然后,在你需要使用布隆过滤器解决缓存穿透的地方,注入 BloomFilterUtil,并使用它来判断数据是否存在于缓存中: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class CacheController { @Autowired private BloomFilterUtil bloomFilterUtil; @GetMapping("/data/{key}") public String getData(@PathVariable String key) { // 先使用布隆过滤器判断key是否可能存在于缓存中 if (bloomFilterUtil.mightContain(key)) { // 如果可能存在,再从缓存中获取数据 String data = redisTemplate.opsForValue().get(key); if (data != null) { return data; } } // 如果数据不在缓存中,进行其他操作(例如从数据库中查询数据) // ... return null; } } ``` 这样,当有大量的请求同时访问某个缓存时,在经过布隆过滤器的判断后,可以避免无效的缓存查询请求,减轻了数据库的负载压力。 请注意,以上代码只是示例,实际使用时需要根据具体的业务需求进行适当的修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值