简介
简单来说,布隆过滤器(BloomFilter)是一种数据结构。特点是存在性检测,如果布隆过滤器中不存在,那么实际数据一定不存在;如果布隆过滤器中存在,实际数据不一定存在。相比于传统数据结构(如:List、Set、Map等)来说,它更高效,占用空间更少。缺点是它对于存在的判断是具有概率性。
原理
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组(或者叫位向量)和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k。
以上图为例,具体的插入数据和校验是否存在的流程:
假设集合里面有3个元素{x, y, z},哈希函数的个数为3。
Step1:将位数组初始化,每位都设置为0。
Step2:对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,哈希值对应位数组上面的一个点,将该位置标记为1。
Step3:查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。
Step4:如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。
应用场景
高效在海量数据中做存在性判断,例如爬虫url去重。
如果需要在分布式应用中做存在性判断,可以用redis实现布隆过滤器。
用redis防止缓冲穿透而使用了布隆过滤器
例如:有一个需求,分布式处理数据以后,然后存储mysql中,但是即便在入库前查询数据库做存在判断,还是会出现很多插入重复异常,因为无法确报本进程做完查重操作后其他进程是否插入了相同数据。因此在入库前再用redis的布隆过滤器做一次存在判断,存在说明数据插入,不存在则说明数据没有插入,同时对布隆过滤器做添加。
布隆过滤器为什么可以防止缓冲穿透
解答这个问题前,先来看看缓冲穿透是怎么回事。百度百科给的概念是:“缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。”,由此可见,缓冲穿透的特点是访问查询的数据一定在缓冲和数据库中都不存在。而一般在数据库存在的数据会通过配置自动同步或更新到缓存中,如果数据库中不存在的数据,那么就不会同步到缓存中,自然缓存中也不存在。反过来说,缓存中不存在的数据,数据库中肯定不存在。所以,当这样不存在的数据到达缓存层经过不存在的过滤,并及时返回结果,这样的数据自然也不会到达数据库的。布隆过滤器虽然对存在数据的过滤具有误报率的缺点,但是对数据做不存在的过滤是100%准确的。所以布隆过滤器可以防止缓存穿透。而且前面简介中提到了它的优点是高效,占用空间更少。尤其针对上亿级数据,在高并发场景下,,它的性能更优。
实现
guava实现
依赖
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>22.0</version> </dependency>
代码实现
import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.Funnels;
public class GuavaBloomFilter {
public static void main(String[] args) {
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),100000,0.01);
bloomFilter.put("10086");
System.out.println(bloomFilter.mightContain("123456"));
System.out.println(bloomFilter.mightContain("10086"));
}
}
hutool实现
依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.0</version>
</dependency>
代码实现
// 初始化
BitMapBloomFilter filter = new BitMapBloomFilter(10);
filter.add("123");
filter.add("abc");
filter.add("ddd");
// 查找
filter.contains("abc")
Redission实现
依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.7</version> </dependency>
代码实现
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class RedissonBloomFilter {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.10.182:6379");
config.useSingleServer().setPassword("isi_redis_123");
//构造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将号码10086插入到布隆过滤器中
bloomFilter.add("10086");
//判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("123456"));//false
System.out.println(bloomFilter.contains("10086"));//true
redisson.shutdown();
}
}
Redis自定义实现
依赖
<!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency>
代码实现
import redis.clients.jedis.Jedis;
public class RedisBloomFilter {
// 初始化集合长度
private static final int length = Integer.MAX_VALUE;
// 准备hash计算次数
private static final int HASH_LENGTH = 5;
/**
* 准备自定义哈希算法需要用到的质数,因为一条数据需要hash计算5次 且5次的结果要不一样
*/
private static int[] primeNums = new int[] { 17, 19, 29, 31, 37 };
/**
* 添加元素到bitSet中
* @param target 要判定是否存在的元素
* @param bit_key 布隆过滤器key
* @param jedis
*/
public static void addKey(String target,String bit_key,Jedis jedis) {
for (int i : primeNums) {
// 计算hashcode
int hashcode = hash(target, i);
// 计算映射在bitset上的位置
int bitIndex = hashcode & (length - 1);
jedis.setbit(bit_key, bitIndex, true);
}
}
/**
* 判断bitSet中是否有被查询的的key(经过hash处理之后的)
* @param target 要判定是否存在的元素
* @param bit_key 布隆过滤器key
* @param jedis
* @return
*/
public static boolean hasKey(String target,String bit_key,Jedis jedis) {
for (int i : primeNums) {
// 计算hashcode
int hashcode = hash(target, i);
// 计算映射在bitset上的位置
int bitIndex = hashcode & (length - 1);
// 只要有一个位置对应不上,则返回false
if (!jedis.getbit(bit_key, bitIndex)) {
return false;
}
}
return true;
}
/**
* 自定义hash函数
* @param target
* @param prime
* @return
*/
private static int hash(String target,int prime) {
int h = 0;
char[] value = target.toCharArray();
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = prime * h + val[i];
}
}
return h;
}
public static void main(String[] args) {
Jedis jedis = RedisConfig.getConn();
String bit_key="bloom2";
RedisBloomFilter.addKey("username",bit_key,jedis);
RedisBloomFilter.addKey("username1",bit_key,jedis);
System.out.println(RedisBloomFilter.hasKey("username",bit_key,jedis));//true
System.out.println(RedisBloomFilter.hasKey("username1",bit_key,jedis));//true
System.out.println(RedisBloomFilter.hasKey("username2",bit_key,jedis));//false
RedisConfig.close(jedis);
}
}
来源
布隆过滤器的原理_快马扬鞭的博客-CSDN博客_布隆过滤器原理
java redis 布隆过滤器_Hefei19881002的博客-CSDN博客