package com.jmdf.world;
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.Charset;
@Component
public class BloomFilter {
@Resource
private RedisConfig redisConfig;
//预计插入量
private long expectedInsertions = 1000;
//可接受的错误率
private double fpp = 0.001F;
//布隆过滤器的键在Redis中的前缀 利用它可以统计过滤器对Redis的使用情况
private String redisKeyPrefix = "bf:";
private Jedis jedis;
//利用该初始化方法从Redis连接池中获取一个Redis链接
@PostConstruct
public void init(){
this.jedis = redisConfig.getRedisUtil().getJedis();
}
public void setExpectedInsertions(long expectedInsertions) {
this.expectedInsertions = expectedInsertions;
}
public void setFpp(double fpp) {
this.fpp = fpp;
}
public void setRedisKeyPrefix(String redisKeyPrefix) {
this.redisKeyPrefix = redisKeyPrefix;
}
//bit数组长度
private long numBits = optimalNumOfBits(expectedInsertions, fpp);
//hash函数数量
private int numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
//计算hash函数个数 方法来自guava
private int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
//计算bit数组长度 方法来自guava
private long optimalNumOfBits(long n, double p) {
if (p == 0) {
p = Double.MIN_VALUE;
}
return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}
/**
* 判断keys是否存在于集合where中
*/
public boolean isExist(String where, String key) {
long[] indexs = getIndexs(key);
boolean result;
//这里使用了Redis管道来降低过滤器运行当中访问Redis次数 降低Redis并发量
Pipeline pipeline = jedis.pipelined();
try {
for (long index : indexs) {
pipeline.getbit(getRedisKey(where), index);
}
result = !pipeline.syncAndReturnAll().contains(false);
} finally {
try {
pipeline.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (!result) {
put(where, key);
}
return result;
}
/**
* 将key存入redis bitmap
*/
private void put(String where, String key) {
long[] indexs = getIndexs(key);
//这里使用了Redis管道来降低过滤器运行当中访问Redis次数 降低Redis并发量
Pipeline pipeline = jedis.pipelined();
try {
for (long index : indexs) {
pipeline.setbit(getRedisKey(where), index, true);
}
pipeline.sync();
} finally {
try {
pipeline.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 根据key获取bitmap下标 方法来自guava
*/
private long[] getIndexs(String key) {
long hash1 = hash(key);
long hash2 = hash1 >>> 16;
long[] result = new long[numHashFunctions];
for (int i = 0; i < numHashFunctions; i++) {
long combinedHash = hash1 + i * hash2;
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
result[i] = combinedHash % numBits;
}
return result;
}
/**
* 获取一个hash值 方法来自guava
*/
private long hash(String key) {
Charset charset = Charset.forName("UTF-8");
return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
}
private String getRedisKey(String where) {
return redisKeyPrefix + where;
}
}
实现原理:
where 是用来存放bigbit的一个二进制集合
key 查询的目标key
数据放入阶段:
根据要放入的数据量、误差率:预估~
计算所需要的bit数组的位数,where是bit容器的键名
所有的数据都存储在这个bit集合里,判断是否存在
是根据目标key的hash后分配在bit集合的坐标,如果这个坐标存在,则这条数据存在。
一旦存入成千上万个位数据到Redis后,千万不要期待使用循环的方式一一清空bitmap的数据,最简单那就是给bitmap的key设置过期时间,只要执行
EXPIRE "bitmap的key值" 0
那么这个bitmap的所有已设置的位就能都清空了!!执行时间不超过1秒。这算是过期时间的一个妙用吧,以前一直都只是简单的当成过期时间而已,没想到在删除数据方面非常高效。