文章目录
- 一、前言
- 二、项目背景
- 三、实现方案
- 1. 谷歌 布隆过滤器
- 2. Redis 布隆过滤器
- 四、思路延伸
- 1. 布隆过滤器的实现原理
- 2. 布隆过滤器的一些扩展
- 3. 布谷鸟过滤器
- 五、参考内容
一、前言
本系列用来记录一些在实际项目中的小东西,并记录在过程中想到一些小东西,因为是随笔记录,所以内容不会过于详细。
二、项目背景
甲方要求,每一个客户的每一份订单里的每个商品如果第一次出现商品金额大于200w 时,需要提示信息。为了保证项目灵活性(,实际实现是通过数据库记录每一份商品出现的最高价格以及时间,同时在缓存中记录着每个客户每个商品当前最大的金额数量。这样如果亲爱的(善变的 )甲方哪一天要求金额变成为100w或者只比对近一年的单子的商品单价时都可以做及时变更,只需要将Redis 中的缓存清一下重新计算即可。
虽说按照上述方案实现了,但是其实当时想到了使用布隆过滤器的方案,当商品金额大于 200w 时将商品放入布隆过滤器中,下次判断如果布隆过滤器中不存在则说明当前商品没有出现过200w金额的订单。但实际上,这里是不适合使用布隆过滤器的,一是布隆过滤器会出现假阳性情况,二是无法处理上面提到的甲方可能的需求变更(事实证明,甲方真的变了 )。
所以本篇只是为了使用布隆过滤器实现玩一玩。
布隆过滤器,Bloom Filter是1970年由Bloom提出的,它是由一组哈希(Hash)函数和一个位阵列组成。布隆过滤器可以用于查询一个元素是否存在于一个集合当中,查询结果为以下二者之一:
- 这个元素可能存在于这个集合当中。
- 这个元素一定不存在于这个集合当中。
布隆过滤器的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器在实际中主要用来解决网页URL去重复,垃圾邮件检测,大集合中重复元素判断和缓存击穿等问题。
三、实现方案
1. 谷歌 布隆过滤器
谷歌在 guava 依赖包中实现了基于内存的布隆过滤器,如下:
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId></dependency>
Demo 如下:
public static void main(String[] args) {int bfSize = 10000;// 创建布隆过滤器,过滤器的元素类型为 Integer, 初始大小为 1000000,差错率为 0.01final BloomFilter<Integer> bloomFilter =BloomFilter.create(Funnels.integerFunnel(), bfSize, 0.01);// 初始化 布隆过滤器的元素for (int i = 0; i < bfSize; i++) {bloomFilter.put(i);}int count = 0;for (int i = 0; i < bfSize; i++) {if (!bloomFilter.mightContain(i)){count++;}}System.out.println("逃脱的数量 :" + count);count = 0;for (int i = bfSize; i < bfSize + 10000; i++) {if (bloomFilter.mightContain(i)) {count++;}}System.out.println("误伤的数量 :" + count);}
2. Redis 布隆过滤器
public static void main(String[] args) {int bfSize = 10000;Config config = new Config();config.useSingleServer().setTimeout(10000).setDatabase(0).setAddress("redis://127.0.0.1:6379");RedissonClient redissonClient = Redisson.create(config);final RBloomFilter<Object> bloomFilter =redissonClient.getBloomFilter("default");bloomFilter.tryInit(bfSize, 0.01);for (int i = 0; i < bfSize; i++) {bloomFilter.add(i);}int count = 0;for (int i = 0; i < bfSize; i++) {if (!bloomFilter.contains(i)){count++;}}System.out.println("逃脱的数量 :" + count);count = 0;for (int i = bfSize; i < bfSize + bfSize; i++) {if (bloomFilter.contains(i)) {count++;}}System.out.println("误伤的数量 :" + count);}
四、思路延伸
1. 布隆过滤器的实现原理
布隆过滤器的实现其实很简单,下面直接参考其他人的总结(来源:布隆过滤器(Bloom Filter)详解、
布隆过滤器(BloomFilter)详解)
一个Bloom Filter是基于一个m位的位阵列(b1,…bm),这些位阵列的初始值为0。另外,还有一系列的hash函数(h1,…hk),这些hash函数的值域属于1~m。下图是一个bloom filter插入x,y,z并判断某个值w是否在该数据集的示意图:
上图中,m=18,k=3;
插入x时,三个hash函数分别得到蓝线对应的三个值,并将对应的位向量改为1,插入y,z时,类似的,分别将红线,紫线对应的位向量改为1。
查找时,当查找x时,三个hash值对应的位向量都为1,因此判断x在此数据集中。y,z也是如此。但是当查找w时,w有个hash值对应的位向量为0,因此可以判断不在此集合中。但是,假如w的最后那个hash值比上图中的大1,这是就会认为w在此集合中,而事实上,w可能不在此集合中,因此可能出现误报。显然的,插入数据越多,1的位数越多,误报的概率越大。
总结:
- 布隆过滤器由位图和n个hash函数构成。布隆过滤器的操作是一个key经过多个hash函数,然后对位图大小进行取余等到多个槽位并对应置为1。判断时只要有一个槽位为0就一定不存在该key。(需要注意的是多个Hash 函数并非时多种Hash算法,而是由一个Key处理结束后经过某种处理后再次进行Hash,位图的大小和 Hash 的次数跟初始化布隆过滤器的大小和指定的误差率有关)
- 布隆过滤器能确定一个key一定不存在,不能判断key一定存在,但可控假阳率确定存在。
- 布隆过滤器不支持删除操作,可以通过两个布隆过滤器解决(依然存在假阳率,但会低一些),添加放在第一个布隆过滤器,删除放在第二个布隆过滤器。即要判断key是否存在,首先检查第二个布隆过滤器是否删除过,如果删除过就往第一个布隆过滤器插入。
布隆过滤器根据n和p算出m和k,hash函数个数是利用开放寻址法来计算的。
2. 布隆过滤器的一些扩展
以下思路参考 布隆过滤器扩容:布隆过滤器由于本身的不可逆性无法完成删除、扩容等操作,因此当 一个布隆过滤器容量到达上限后,创建一个更大的布隆过滤器,将新增的数据都存入新的布隆过滤器中,这样多个布隆过滤器组成过滤器链,判断Key 是否存在的时候需要判断所有的过滤器。此种方案还可以用于带有时效性的布隆过滤器,如需要判断最近30天内的,即可以创建30个布隆过滤器,每个过滤器存储一天的数据,当30天后将第一个过滤器清除重新使用。
3. 布谷鸟过滤器
布谷鸟过滤器可以说是一个增强版的布隆过滤器,可以删除元素,查询效率更高,空间利用率更高。如有需要详参: 布隆过滤器和布谷鸟过滤器详解
五、参考内容
https://blog.csdn.net/LT11hka/article/details/129063873
https://www.xjx100.cn/news/726331.html
https://zhuanlan.zhihu.com/p/616911933
https://www.cnblogs.com/shoshana-kong/p/14759488.html