Bloom filter 1. Introduction
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难
重点 :
二进制向量 : 二进制的方式存放数据
随机映射函数 : 多次运算算出位数
误判率 : 算出有不一定有 , 算出 没有 一定 没有
删除困难 : 多次hash 导致删除麻烦
布隆过滤器不存放数据 , 只存放数据经过运算后的值
经过m次运算后 把对应bit位设置为1
Bloom filter implemented embodiment 2.java
分布式布隆过滤器 :
redis实现
1.bit实现
2.redis插件实现(4.0之后) https://www.codercto.com/a/87070.html
本地布隆过滤器 :
guava实现
Bloom filter using 3.guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
创建布隆过滤器方法
public static <T> BloomFilter<T> create(
Funnel<? super T> funnel, int expectedInsertions, double fpp) {
funnel : 添加数据的接口
expectedInsertions : 预计添加多少数据
fpp : 能接受的误判率
//创建
BloomFilter<String> filter = BloomFilter.create(new Funnel<String>() {
@Override
public void funnel(String from, PrimitiveSink into) {
System.out.println("testadd");
into.putString(from, Charsets.UTF_8);
}
}, 50, 0.1);
//添加元素
filter.put("test");
//判断元素是否存在
boolean test = filter.mightContain("test");
//把另一个布隆过滤器的数据加进来
filter.putAll();
Bloom filter using 4.redis
1.redis Bloom filter to create entity classes
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;
import java.nio.charset.Charset;
public class RedisBloomFilter {
private int numApproxElements; //预计存放数据个数
private double fpp; //误判率
private int numHashFunctions; //计算几次hash
private int bitmapLength; //布隆过滤器的容量
/**
* 构造布隆过滤器。注意:在同一业务场景下,三个参数务必相同
*
* @param numApproxElements 预估元素数量
* @param fpp 可接受的最大误差(假阳性率)
*/
public RedisBloomFilter(int numApproxElements, double fpp) {
this.numApproxElements = numApproxElements;
this.fpp = fpp;
bitmapLength = (int) (-numApproxElements * Math.log(fpp) / (Math.log(2) * Math.log(2)));
numHashFunctions = Math.max(1, (int) Math.round((double) bitmapLength / numApproxElements * Math.log(2)));
}
/**
* 取得自动计算的最优哈希函数个数
*/
public int getNumHashFunctions() {
return numHashFunctions;
}
/**
* 取得自动计算的最优Bitmap长度
*/
public int getBitmapLength() {
return bitmapLength;
}
/**
* 计算一个元素值哈希后映射到Bitmap的哪些bit上
*
* @param element 元素值
* @return bit下标的数组
*/
public Long[] getBitIndices(String element) {
Long[] indices = new Long[numHashFunctions];
byte[] bytes = Hashing.murmur3_128()
.hashObject(element, Funnels.stringFunnel(Charset.forName("UTF-8")))
.asBytes();
long hash1 = Longs.fromBytes(
bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]
);
long hash2 = Longs.fromBytes(
bytes[15], bytes[14], bytes[13], bytes[12], bytes[11], bytes[10], bytes[9], bytes[8]
);
long combinedHash = hash1;
for (int i = 0; i < numHashFunctions; i++) {
indices[i] = (combinedHash & Long.MAX_VALUE) % bitmapLength;
combinedHash += hash2;
}
return indices;
}
}
2. Ideas
使用redis bit操作 创建布隆过滤器 , 保存redis布隆对象在redis中
//初始化操作 设置一个名字为redisbit 的redis bit 长度为10W
setbit redisbit 100000 0
//插入操作
使用guava的计算方法 , 获取到计算后的bit位 , 全部设置到redis中
//判断是否存在
使用guava的计算方法 , 获取到计算后的bit位 , 判断redis中是否均为1
3.redis Tools
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* @author
* @since 2019—11-19
*/
@Component
@Slf4j
public class RedisBloomFilterUtil {
private static RedisTemplate redisTemplate;
private static SetOperations<String, String> setOperations;
private static ValueOperations<String, String> valueOperations;
public static void initBit(String bitKey, int bitmapLength) {
valueOperations.setBit(bitKey, bitmapLength, false);
}
@Autowired
public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
RedisBloomFilterUtil.redisTemplate = redisTemplate;
setOperations = redisTemplate.opsForSet();
valueOperations = redisTemplate.opsForValue();
}
/**
* 初始化 布隆过滤器
* @param indexs 要初始化的bit位集合
* @param key rediskey值
* @param size 每次操作多少
*/
public static void setAllBit(Set<Long> indexs, String key, int size) {
List<Long> indexList = new LinkedList();
for (Long index : indexs) {
indexList.add(index);
if (indexList.size() == size) {
addBitPip(indexList, key);
indexList.clear();
}
}
addBitPip(indexList, key);
indexList.clear();
}
//添加元素
public static void addBitPip(List<Long> list, String keys) {
List<Object> objects = redisTemplate.executePipelined((RedisCallback<Long>) connection -> {
for (Long index : list) {
connection.setBit(keys.getBytes(), index, true);
}
return null;
});
}
//判断元素是否存在
public static boolean bitMembers(Long[] list, byte[] key) {
List<Boolean> pipelined = redisTemplate.executePipelined((RedisCallback<Boolean>) connection -> {
for (Long index : list) {
connection.getBit(key, index);
}
return null;
});
return !pipelined.contains(false);
//key计算次数较少时方案
// for (Long index : list) {
// Boolean flag =valueOperations.getBit(key , index);
// if (Boolean.FALSE.equals(flag))
// return false;
// }
// return true;
}
/**
* 批量添加数据到 set 缓存
*/
public static void setOperationsAdd(String key, Collection<String> collection) {
if (collection.size() <= 0) {
return;
}
byte[] keyBytes = key.getBytes();
if (collection.size() > 3000) {
List<String> list = new LinkedList<>();
for (String s : collection) {
list.add(s);
if (list.size() >= 3000) {
redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(list)));
list.clear();
}
}
redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(list)));
} else {
redisTemplate.execute((RedisCallback<Long>) redisConnection -> redisConnection.sAdd(keyBytes, getBytes(collection)));
}
}
public static byte[][] getBytes(Collection<String> list) {
byte[][] values = new byte[list.size()][];
int i = 0;
for (String s : list) {
values[i++] = s.getBytes();
}
return values;
}
/**
* 判断set缓存是否存在field
*/
public static Boolean setOperationsIsMember(String key, String field) {
Boolean flag = setOperations.isMember(key, field);
return flag;
}
}
4. Test results
set --- bit 对比
模拟100W udid基础数据
最优 hash个数13
最优 bitmap长度38340233
每次bit插入数量 : 10000
创建udid个数 : 1000000
插入set 时间 : 2.582 s
计算bit时间 : 10.97 s
总计数量(bit计算后) : 11024880
创建bit 时间 : 1.695 min
创建idfa(用来计算误判率) : 1000000
创建idfa 时间 : 2.832 s
set判断存在 count : 0
set判断 时间 : 1.903 min
判断bit 时间 : 4.627 min
bit判断存在 count : 1582
误判率 : 0.001582
//1000W udid 内存对比 (bit 191701167 个位)
set : 800M
bit : 25m