1、配置类
import com.google.common.base.Charsets;
import com.google.common.hash.Funnel;
import com.luban.common.constant.RedisKeyPrefixConst;
import com.luban.mall.component.BloomRedisService;
import com.luban.mall.service.PmsProductService;
import com.luban.mall.util.BloomFilterHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Slf4j
@Configuration
public class BloomFilterConfig implements InitializingBean{
@Autowired
private PmsProductService productService;
@Autowired
private RedisTemplate template;
@Bean
public BloomFilterHelper<String> initBloomFilterHelper() {
return new BloomFilterHelper<>((Funnel<String>) (from, into) -> into.putString(from, Charsets.UTF_8)
.putString(from, Charsets.UTF_8), 1000000, 0.01);//误差率
}
/**
* 布隆过滤器bean注入
* @return
*/
@Bean
public BloomRedisService bloomRedisService(){
BloomRedisService bloomRedisService = new BloomRedisService();
bloomRedisService.setBloomFilterHelper(initBloomFilterHelper());
bloomRedisService.setRedisTemplate(template);
return bloomRedisService;
}
@Override
public void afterPropertiesSet() throws Exception {
List<Long> list = productService.getAllProductId();
log.info("加载产品到布隆过滤器当中,size:{}",list.size());
if(!CollectionUtils.isEmpty(list)){
list.stream().forEach(item->{
// LocalBloomFilter.put(item);
bloomRedisService().addByBloomFilter(RedisKeyPrefixConst.PRODUCT_REDIS_BLOOM_FILTER,item+"");
});
}
}
}
2、布隆拦截器
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luban.common.api.CommonResult;
import com.luban.common.constant.RedisKeyPrefixConst;
import com.luban.mall.component.BloomRedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Slf4j
public class BloomFilterInterceptor implements HandlerInterceptor {
@Autowired
private BloomRedisService bloomRedisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String currentUrl = request.getRequestURI();
PathMatcher matcher = new AntPathMatcher();
//解析出pathvariable
Map<String, String> pathVariable = matcher.extractUriTemplateVariables("/pms/productInfo/{id}", currentUrl);
//布隆过滤器存储在redis中
if(bloomRedisService.includeByBloomFilter(RedisKeyPrefixConst.PRODUCT_REDIS_BLOOM_FILTER,pathVariable.get("id"))){
return true;
}
/**
* 存储在本地jvm布隆过滤器中
*/
/* if(LocalBloomFilter.match(pathVariable.get("id"))){
return true;
}*/
/*
* 不在本地布隆过滤器当中,直接返回验证失败
* 设置响应头
*/
response.setHeader("Content-Type","application/json");
response.setCharacterEncoding("UTF-8");
String result = new ObjectMapper().writeValueAsString(CommonResult.validateFailed("产品不存在!"));
response.getWriter().print(result);
return false;
}
}
3、本地布隆过滤器(生产不用)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.StandardCharsets;
public class LocalBloomFilter {
private static final BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),1000000,0.01);
/**
* 谷歌guava布隆过滤器
* @param id
* @return
*/
public static boolean match(String id){
return bloomFilter.mightContain(id);
}
public static void put(Long id){
bloomFilter.put(id+"");
}
}
4、redis布隆
import com.google.common.base.Preconditions;
import com.luban.mall.util.BloomFilterHelper;
import org.springframework.data.redis.core.RedisTemplate;
public class BloomRedisService {
private RedisTemplate<String, Object> redisTemplate;
private BloomFilterHelper bloomFilterHelper;
public void setBloomFilterHelper(BloomFilterHelper bloomFilterHelper) {
this.bloomFilterHelper = bloomFilterHelper;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 根据给定的布隆过滤器添加值
*/
public <T> void addByBloomFilter(String key, T value) {
Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
int[] offset = bloomFilterHelper.murmurHashOffset(value);
for (int i : offset) {
redisTemplate.opsForValue().setBit(key, i, true);
}
}
/**
* 根据给定的布隆过滤器判断值是否存在
*/
public <T> boolean includeByBloomFilter(String key, T value) {
Preconditions.checkArgument(bloomFilterHelper != null, "bloomFilterHelper不能为空");
int[] offset = bloomFilterHelper.murmurHashOffset(value);
for (int i : offset) {
if (!redisTemplate.opsForValue().getBit(key, i)) {
return false;
}
}
return true;
}
}
5、redis布隆依赖
import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;
/**
* 算法过程:
* 1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
* 2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
* 3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
* 4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
**/
public class BloomFilterHelper<T> {
private int numHashFunctions;
private int bitSize;
private Funnel<T> funnel;
public BloomFilterHelper(Funnel<T> funnel, int expectedInsertions, double fpp) {
Preconditions.checkArgument(funnel != null, "funnel不能为空");
this.funnel = funnel;
// 计算bit数组长度
bitSize = optimalNumOfBits(expectedInsertions, fpp);
// 计算hash方法执行次数
numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, bitSize);
}
public int[] murmurHashOffset(T value) {
int[] offset = new int[numHashFunctions];
long hash64 = Hashing.murmur3_128().hashObject(value, funnel).asLong();
int hash1 = (int) hash64;
int hash2 = (int) (hash64 >>> 32);
for (int i = 1; i <= numHashFunctions; i++) {
int nextHash = hash1 + i * hash2;
if (nextHash < 0) {
nextHash = ~nextHash;
}
offset[i - 1] = nextHash % bitSize;
}
return offset;
}
/**
* 计算bit数组长度
*/
private int optimalNumOfBits(long n, double p) {
if (p == 0) {
// 设定最小期望长度
p = Double.MIN_VALUE;
}
return (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
}
/**
* 计算hash方法执行次数
*/
private int optimalNumOfHashFunctions(long n, long m) {
return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
}
}