1.背景
最近在做一个基于大数据量的应用,如果数据存在就不写入,反之写入数据。逻辑上来看其实比较简单,无非就是每次插入的时候,查询数据库这条数据是否存在,然后在根据是否存在处理后续的逻辑。但是也具有一个比较大的挑战,就是每次请求来了我都要去查询数据库,数据库负载比较高,而且数据量大的情况下效率也随之降低了。有人说这可以通过数据库建立索引提升查询效率,虽说是一种方案,但也会存在不利的一面,建立索引会加大额外的存储空间,还会影响数据插入效率,是对立的,那有没有一种更好的方法在不动底层数据库架构的情况下进行优化呢?
2.问题分析
我们的目标是在尽量以最小的影响现有状态的情况下,提升程序的查询效率。肯定有人说那就直接上缓存,我觉得吧,这是对的但也不对,主要还是要看如何使用缓存,如果缓存使用方式正确确实会大大提高效率,反之亦然。这里推荐使用布隆过滤器,他可以结合Guava或者redis进行实现,确实可以较大的提升程序效率。
布隆过滤器:是一个在大数据中常用的概率算法,其应用场景为判断一个元素是否已经存在数据库中了,这是一个有效的减少数据库IO的方法。如果要判断数据库是否存在某一个数据,一个一个比较是可行的办法,但是这样比较慢,特别是当数据库里面的数据很多的时候,甚至是大数据的时候更可怕,一个一个比较效率会非常低下。如果,应用不要求百分之百的正确性,允许一定程度的误报(万分之一,或十万分之一)(这个误判率是可以设置的,Guava工具类已经实现布隆过滤器)误判率越小所需时间越长(性能差),那么布隆过滤器用于提升效率正合适。
3.布隆过滤器基本原理
布隆过滤器的基本思想: 利用多个不同的哈希函数将一个元素映射到多个不同的位(或桶)上,并将这些位设置为1. 当查询一个元素是否在布隆过滤器中时,将这个元素进行哈希操作,得到多个哈希值,并检查这些哈希值对应的位是否都为1。如果有任意一位为0,则可以确定该元素不在布隆过滤器中;如果多有部位都为1,则不能确定该元素一定在布隆过滤器中。
为什么会出现误报率呢?我认为是在进行哈希算法的时候,可能存在哈希冲突,将不同的元素被映射到了相同的位上。本来是不存在的,但是哈希映射出来是存在的,就产生了误报。
4.问题解决办法
我这里按照Guava实现一个布隆过滤器
第一步:导入需要的Guava包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version> <!-- 使用你需要的版本 -->
</dependency>
第二步:使用Guava布隆过滤器代码示例
package com.test.test;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author dp
* @date 2024/9/13
* 布隆过滤器误判率测试案例
*/
public class BloomFilterTest {
//日志,用于执行记录时间
private static final Log log = LogFactory.getLog(BloomFilterTest.class);
//模拟布隆过滤器预计存储数据的大小,这里设置为100万
private static int size = 1000000;
//设置 期望的误判率
private static double error = 0.01;
/**
* 创建布隆过滤器
* Integer:表示过滤器存储的是整数类型的数据
* Funnels.integerFunnel():表示过滤器只对整数类型进行判断
*/
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, error);
//设置样本数据为100万
private static int total = 1000000;
public static void main(String[] args) {
//1、在布隆过滤器中,插入样本数据
for (int i = 0; i < total; i++) {
//使用put()插入即可
bloomFilter.put(i);
}
//2、定义一个误判数据量的计量值
int count = 0;
//3、误判测试前,打印下一句话,用于测试误判测试时间
log.warn("误判开始");
/**
* 4、添加10万个布隆过滤器中不存在的数据,用于测试误判率
*
* 这里直接在total的基础上,再加10万的数据
*/
for (int i = total; i < total+100000; i++) {
//mightContain()判断数据是否存在于布隆过滤器中
if (bloomFilter.mightContain(i)) {
count++;
//System.out.println("数据:" + i + "误判了");
}
}
log.warn("误判结束");
//打印误判数据及耗时
System.out.println("总共误判数为:" + count);
}
}
- 在12ms内执行完成
- 误判数为:947(测试总数为10万)
- 真实误判率为:947 ÷ 10万 = 0.00947 ≈ 0.01 ,符合预期
总结:设置的误判率是能直接影响布隆过滤器最终的误判率的,误判率越低,耗时越多
问题:我们是不是把误判率设置为无限小,那么就不存在误判了呢?
这要双面来看,如果误判率越小,那么程序设置的Hash桶就会越大,一方面会占用更大的空间,另一方面在进行hash比对的时候也会消耗更多的时间。
5.反过来想一下
是否使用布隆过滤器?以及使用布隆过滤器时设置误判率为多大?我的理解还是得看业务,如果业务不允许 哪怕一丁点的错误,我觉得损耗性能是值得的,比如:财务中,涉及到金额数据,我们不允许哪怕一分钱对不上,此时我们就不能使用这个布隆过滤器的方法。但是在另外一些场景下,即使存在误判,也不影响数据最终的结果,或者业务本身就可以允许一定的偏差,我觉得还是可以考虑使用布隆过滤器。总之,一切的技术都是为业务服务的,业务才是主导技术的应用,不能为了体现技术的高大尚而不考虑业务的场景。