springboot中使用布隆过滤器BloomFilter

导读:本篇文章讲解 springboot中使用布隆过滤器BloomFilter,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

环境:springboot2.3.8.RELEASE+Redis

bloomfilter相关知识参考

springboot中使用布隆过滤器BloomFilter

 

布隆过滤器(Bloom Filter)是由布隆(Burton Howard Bloom)在1970年提出的。它实际上是由一个很长的二进制向量(位向量)和一系列随机映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率(假正例False positives,即Bloom Filter报告某一元素存在于某集合中,但是实际上该元素并不在集合中)和删除困难,但是没有识别错误的情形(即假反例False negatives,如果某个元素确实没有在该集合中,那么Bloom Filter 是不会报告该元素存在于集合中的,所以不会漏报)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。

BloomFilter在判断数据是否时,如果判断的数据存在那有可能不存在,如果不存在那就是真的不存在。为何?BloomFilter本身就是用一个很长的二进制位来表示数据是否存在。在标记一个数据时是通过N个Hash函数来计算当前的值,每一个hash函数将对应的值进行计算会得到一个整数,然后就把对应的整数位设置为1。所以当你通过N个Hash函数计算后返回数据存在,那有可能是不存在的,因为有可能某一个二进制位可能是其它某个数据计算后设置为1的。如果二进制位足够长,使用的Hash函数比较得多,那么错误率就相对的比较少,但是这样效率就会相应的降低。

优点:

相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数,取决于hash函数的个数k(O(k))。另外, Hash 函数相互之间没有关系,方便并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

缺点:

布隆过滤器的缺点和优点一样明显。误算率(False Positive)是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

以上简单介绍了BloomFilter,接下来看看如何在项目中使用。


  • 相关依赖
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
		</dependency>
		<dependency>
		    <groupId>com.google.guava</groupId>
		    <artifactId>guava</artifactId>
		    <version>30.1-jre</version>
		</dependency>
	</dependencies>
  • 相关配置
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 3 
    lettuce:
      pool:
        maxActive: 8
        maxIdle: 100
        minIdle: 10
        maxWait: -1
---
# 自定义相关的属性配置          
bf:
  expected-insertions: 10001
  fpp: 0.01     
@Component
@ConfigurationProperties(prefix = "bf")
public class BloomFilterProperties {
	/**
	 * 预期插入量
	 */
	private Long expectedInsertions = 1000L;
	/**
	 * 误判率(大于0,小于1.0)
	 */
	private Double fpp = 0.001D;

	public Long getExpectedInsertions() {
		return expectedInsertions;
	}

	public void setExpectedInsertions(Long expectedInsertions) {
		this.expectedInsertions = expectedInsertions;
	}

	public Double getFpp() {
		return fpp;
	}

	public void setFpp(Double fpp) {
		this.fpp = fpp;
	}
}
  • BloomFilter核心类
@Component
public class RedisBloomFilter {

	/**
	 * 	二进制位大小(多少位)
	 */
	private Long numBits ;
	/**
	 * 	hash函数个数
	 */
	private Integer numHashFunctions ;
	
	private Funnel<CharSequence> funnel = Funnels.stringFunnel(Charset.forName("UTF-8")) ;

	@Resource
	private StringRedisTemplate stringRedisTemplate ;
	@Resource
	private BloomFilterProperties prop ;
	
	@PostConstruct
	public void initRedisBloomFilter() {
		this.numBits = optimalNumOfBits(prop.getExpectedInsertions(), prop.getFpp()) ;
		this.numHashFunctions = optimalNumOfHashFunctions(prop.getExpectedInsertions(), numBits) ;
	}
	
	/**
	 *  <p>
	 *  	最佳的位数(二进制位数)
	 *  </p>
	 *  <p>时间:2021年2月7日-上午10:13:43</p>
	 * @author xg
	 * @param expectedInsertions 预期插入的数量
	 * @param fpp 错误率大于0,小于1.0
	 * @return long
	 */
	public long optimalNumOfBits(long expectedInsertions, double fpp) {
		if (fpp == 0) {
			fpp = Double.MIN_VALUE;
		}
		return (long) (-expectedInsertions * Math.log(fpp) / (Math.log(2) * Math.log(2)));
	}
	
	/**
	 *  <p>
	 *  	最佳的Hash函数个数
	 *  </p>
	 *  <p>时间:2021年2月7日-上午10:17:26</p>
	 * @author xg
	 * @param expectedInsertions 预期插入的数量
	 * @param numBits 根据optimalNumOfBits方法计算的最佳二进制位数
	 * @return int
	 */
	public static int optimalNumOfHashFunctions(long expectedInsertions, long numBits) {
		return Math.max(1, (int) Math.round((double) numBits / expectedInsertions * Math.log(2)));
	}
	
  // 存数据
	public boolean put(String key, String value) {
		byte[] bytes = Hashing.murmur3_128().hashObject(value, funnel).asBytes();
		long hash1 = lowerEight(bytes);
		long hash2 = upperEight(bytes);

		boolean bitsChanged = false;
		long combinedHash = hash1;
		for (int i = 0; i < numHashFunctions; i++) {
			long bit = (combinedHash & Long.MAX_VALUE) % numBits ;
			// 这里设置对应的bit为1
			stringRedisTemplate.opsForValue().setBit(key, bit, true) ;
			combinedHash += hash2;
		}
		return bitsChanged;
	}

  // 判断数据是否已经存在
	public boolean mightContain(String key, String value) {
		byte[] bytes = Hashing.murmur3_128().hashObject(value, funnel).asBytes();
		long hash1 = lowerEight(bytes);
		long hash2 = upperEight(bytes);

		long combinedHash = hash1;
		for (int i = 0; i < numHashFunctions; i++) {
			long bit = (combinedHash & Long.MAX_VALUE) % numBits ;
			// 这里判断redis中对应位是否为1
			if (!stringRedisTemplate.opsForValue().getBit(key, bit)) {
				return false;
			}
			combinedHash += hash2;
		}
		return true;
	}

	private long lowerEight(byte[] bytes) {
		return Longs.fromBytes(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);
	}

	private long upperEight(byte[] bytes) {
		return Longs.fromBytes(bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]);
	}

}

以上核心类,大部分是从Guava中抄过来的,毕竟相关的数学知识也不懂啊(基本知道也不知道是为啥)。

 

  • 测试
  1. 先存入数据
@Resource
	private RedisBloomFilter redisBloomFilter ;
	
	@GetMapping("/a")
	public Object addItem() {
		IntStream.range(1, 10001).forEach(i -> {
			redisBloomFilter.put("bit.a", UUID.randomUUID().toString()) ;
		});
		return "success" ;
	}

存入10000个数据。

  1. 判断数据是否存在
@GetMapping("/e")
	public Object exist(String value) {
		int errors = 0 ;
		for (int i = 0; i < 100; i++) {
			if (redisBloomFilter.mightContain("bit.a", UUID.randomUUID().toString())) {
				errors += 1 ;
			}
		}
		return errors / 10000d ;
	}

这里我们用100个全新的UUID来判断错误率。

springboot中使用布隆过滤器BloomFilter

 

测试1000个值

springboot中使用布隆过滤器BloomFilter

 

明显错误率上升了,但都控制在0.01。

完毕!!!

给个关注+转发呗谢谢

图片

 

图片

 

图片

 

图片

 

图片

springboot中使用布隆过滤器BloomFilter

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/80034.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!