目录
特别提醒
前面三篇,已经把消息队列和其所包含的Kafka和RabbitMQ做了说明,并用案例演示了如何使用,今天这一篇,我们要讲解的内容是布隆过滤器,布隆过滤器不同于过滤器Filter,想知道布隆过滤器是什么,和怎样使用布隆过滤器吗?今天这篇博客,将带你了解这些,学完这篇,你将能独立使用布隆过滤器,了解其工作原理。下面,就让我们一起来学习吧。
说起过滤器,我们总是想起两种:一种是Filter过滤器,一种是布隆过滤器。Filter过滤器用于在全局捕捉请求,并在请求前后做一些事情,比如:统一编码,URL级别的权限访问控制,过滤敏感词汇,压缩请求信息等。那么布隆过滤器是做什么的呢?这里先卖个关子,我们继续往下看。
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器不保存元素,之判断是否存在,不保存,所以也无法取出元素。
布隆过滤器主要应用于网页URL的去重,垃圾邮件的判别,集合重复元素的判别,查询加速(比如基于key-value的存储系统)、数据库防止查询击穿, 使用BloomFilter来减少不存在的行或列的磁盘查找。
在Java开发 - Redis初体验一文中,我们曾提到过布隆过滤器,在缓存击穿的预防上面它是很好的效果的。刚好,学完此篇,你就可以在Redis项目中尝试添加布隆过滤器来防止缓存击穿,因为在当时写那篇博客时,我们采用的方法是给null值,在文中也提到,这是不合适的,因为当时还没有讲解布隆过滤器,学完此篇,大家可以去做个尝试。
常规检查元素是否存在我们会怎么做?一般是遍历集合,判断元素是否相等,但这种查询的方式在数据量庞大的情况下效率太低,想要实现快速的查找,前面学过的Java开发 - 树(二叉树,二叉排序树,红黑树)中曾经讲解过的数据结构或许会有一些帮助,其Hash散列或类似算法可以保证高效判断元素是否存在,但也存在消耗内存较多的问题,布隆过滤器的存在,正好解决了这个问题。
当要向布隆过滤器中添加一个元素时,该元素会经过N个哈希函数的计算并最终产生k个哈希值,布隆过滤器是个位数组,假设每一位是0,这些值就会根据计算出来的下标放入这个数组中,为1。在查询时,会判断这几个哈希值所在的位置,只要有一个为0就不存在于布隆过滤器中。
下面,我们通过几张图来说明布隆过滤器查询的原理:
假设这是一个空的布隆过滤器:

现在添加了coding单词在布隆过滤器中的状态:
coding算法1加密:1
coding算法2加密:3
coding算法3加密:5

现在添加了fire单词在布隆过滤器中的状态:
fire算法1加密:2
fire算法2加密:4
fire算法3加密:6

现在添加了codingfire单词在布隆过滤器中的状态:
codingfire算法1加密:1
codingfire算法2加密:4
codingfire算法3加密:6

问题来了,我还没添加呢,1,4,6的位置已经有东西了,这时候,布隆过滤器就会误判,认为codingfire已存在于布隆过滤器中。
布隆过滤器误判特点:
这是因为我们做测试时布隆过滤器太短了,假设就10格,会导致每个位置值都是1,我们就不用存东西了,误判会很严重,啥都存在,那这就是个失败的布隆过滤器。所以,合适的大小对布隆过滤器非常重要。
布隆过滤器在启动时需要分配合适的内存大小,这直接决定了它是否准确。设置的大小要满足这几个条件:
关于这两个条件,有一个公式用于计算误判率:

这是根据误判率计算布隆过滤器长度的公式:
n :是已经添加元素的数量;
k :哈希的次数;
m :布隆过滤器的长度(位数的大小)
最终结果结果就是误判率。
反着来也是可以的,知道误判率,反推布隆过滤器长度:

推荐一个在线计算布隆过滤器大小的网站,可以正推,也可以反推:Bloom filter calculator
布隆过滤器安装推荐这篇博客:Redis 布隆过滤器命令的使用详解
不过,它里面提到的布隆过滤器我个人不推荐,因为官方也推出了一个结合了Redis和布隆过滤器的软件工具:redis/redis-stack
网址在:Docker
安装方法和推荐博客里是一样的,由于比较简单,博主就不带大家一步步安装了,安装完请回到这里,我们继续。
安装完记得启动:

由于我们是教程,所以密码不设置,端口采用默认,实际可是要改的。 我们在上一篇RabbitMQ所在的子工程stock中继续集成,做一个简单的测试。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: localhost
port: 6379
password:
在stock包下建utils包,包下新建一个类,代码如下:
package com.codingfire.cloud.stock.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
public class RedisBloomUtils {
@Autowired
private StringRedisTemplate redisTemplate;
private static RedisScript<Boolean> bfreserveScript = new DefaultRedisScript<>("return redis.call('bf.reserve', KEYS[1], ARGV[1], ARGV[2])", Boolean.class);
private static RedisScript<Boolean> bfaddScript = new DefaultRedisScript<>("return redis.call('bf.add', KEYS[1], ARGV[1])", Boolean.class);
private static RedisScript<Boolean> bfexistsScript = new DefaultRedisScript<>("return redis.call('bf.exists', KEYS[1], ARGV[1])", Boolean.class);
private static String bfmaddScript = "return redis.call('bf.madd', KEYS[1], %s)";
private static String bfmexistsScript = "return redis.call('bf.mexists', KEYS[1], %s)";
public Boolean hasBloomFilter(String key){
return redisTemplate.hasKey(key);
}
/**
* 设置错误率和大小(需要在添加元素前调用,若已存在元素,则会报错)
* 错误率越低,需要的空间越大
* @param key
* @param errorRate 错误率,默认0.01
* @param initialSize 默认100,预计放入的元素数量,当实际数量超出这个数值时,误判率会上升,尽量估计一个准确数值再加上一定的冗余空间
* @return
*/
public Boolean bfreserve(String key, double errorRate, int initialSize){
return redisTemplate.execute(bfreserveScript, Arrays.asList(key), String.valueOf(errorRate), String.valueOf(initialSize));
}
/**
* 添加元素
* @param key
* @param value
* @return true表示添加成功,false表示添加失败(存在时会返回false)
*/
public Boolean bfadd(String key, String value){
return redisTemplate.execute(bfaddScript, Arrays.asList(key), value);
}
/**
* 查看元素是否存在(判断为存在时有可能是误判,不存在是一定不存在)
* @param key
* @param value
* @return true表示存在,false表示不存在
*/
public Boolean bfexists(String key, String value){
return redisTemplate.execute(bfexistsScript, Arrays.asList(key), value);
}
/**
* 批量添加元素
* @param key
* @param values
* @return 按序 1表示添加成功,0表示添加失败
*/
public List<Integer> bfmadd(String key, String... values){
return (List<Integer>)redisTemplate.execute(this.generateScript(bfmaddScript, values), Arrays.asList(key), values);
}
/**
* 批量检查元素是否存在(判断为存在时有可能是误判,不存在是一定不存在)
* @param key
* @param values
* @return 按序 1表示存在,0表示不存在
*/
public List<Integer> bfmexists(String key, String... values){
return (List<Integer>)redisTemplate.execute(this.generateScript(bfmexistsScript, values), Arrays.asList(key), values);
}
private RedisScript<List> generateScript(String script, String[] values) {
StringBuilder sb = new StringBuilder();
for(int i = 1; i <= values.length; i ++){
if(i != 1){
sb.append(",");
}
sb.append("ARGV[").append(i).append("]");
}
return new DefaultRedisScript<>(String.format(script, sb.toString()), List.class);
}
}
代码都是模版,使用时直接添加此类即可。
我们quartz包下有个QuartzJob类,我们曾在里面测试Quartz功能和RabbitMQ,现在,我们来添加布隆过滤器的测试代码:
package com.codingfire.cloud.stock.quartz;
import com.codingfire.cloud.stock.utils.RedisBloomUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.LocalDateTime;
public class QuartzJob implements Job {
// RabbitTemplate就是amqp框架提供的发送消息的对象
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private RedisBloomUtils redisBloomUtils;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
//输出当前时间
System.out.println("--------------"+ LocalDateTime.now() +"---------------");
// 先简单的发送一串文字
// rabbitTemplate.convertAndSend(RabbitMQConfig.STOCK_EX,RabbitMQConfig.STOCK_ROUT,"黄河之水天上来,奔流到海不复还。");
// 首先确定要向布隆过滤器中保存的元素
String[] numbers={"zero","one","two","three","four","five","six","seven","eight","nine","ten"};
// 使用RedisBloomUtils类将上面的数组元素保存在Redis(布隆过滤器)中
redisBloomUtils.bfmadd("numberBloom",numbers);
// 下面就可以判断一个元素是否在这个布隆过滤器中了
String elm="six";
System.out.println("布隆过滤器判断"+elm+"是否存在:"+ redisBloomUtils.bfexists("numberBloom",elm));
}
}
大阿甲不用纠结代码为什么写在这个类里,随便你写在哪里,写在测试类中也行,只要你项目能运行我们写的测试方法,随便你写在哪。
下面我们来简单测试下布隆过滤器的使用,看情况启动服务,如果十一路学习过来,和博主用的是相同工程的同学,请启动nacos,seata,mysql,还有最重要的redisbloom,可能还要启动RabbitMQ,否则会报错,启动后,运行项目,查看控制台输出:

能输出图中内容的就是测试成功了。

设置错误率和大小在使用前记得调用,不设置默认0.01,自己看哈。
QuartzJob中布隆过滤器虽然进行了测试,但我们要知道QuartzJob的工作驱动,还有一个类叫QuartzConfig,这里面的代码相当于是注册+触发条件。我这么说,大家懂吗?不懂得可以去看看驱动QuartzJob运行的触发器的设置:Java开发 - Quartz初体验。
简单来说,布隆过滤器就是用来判断某个元素是否存在于某一个集中的东西,而且,其判断效率非常高,是一个在大数据情况下常用的工具,但它本身代码量并不大,但要注意布隆过滤器存在误判率,所以使用起来,在设置重要参数时还是要注意些的。今天的布隆过滤器介绍到此结束, 不知道大家对最近的博客还满意吗?欢迎留言沟通交流。
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s