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有如下优点:
对应地,BloomFilter有如下缺点:
Redis对BloomFilter进行了应用。其实现方式有两种:使用Redis的bitmap实现;使用布隆过滤器插件实现。
根据原理可以知道,BloomFilter需要设置一个初始大小,用于表示预计放入的元素数量。可以想象,该值若过小,则hash冲突的元素就会增多,从而导致误判率的提升。因此该值应设置较大一些。
对一个数据访问,先判断是否在Redis中,若是则返回;若否则查询数据库,并将查到的值设置到Redis中。这样下次查询就可以顺利查到了。
但若一个数据访问,既不在Redis中也不在数据库中,那么该访问将先访问Redis再访问数据库。若再次访问该数据,则该流程会再次重复。
于是,攻击黑客就发送大量针对不存在数据的请求,那么上述访问流程就会反复重复,导致数据库承担大量压力。在该流程中缓存这道墙形同虚设,称为缓存穿透。
BloomFilter就可以很好地解决该问题:一个针对虚假数据的访问,Redis借助BloomFilter可以很明确地判断出该数据一定不在数据库中,于是就直接返回,不再查询数据库。
注意区别二者的概念: