布隆过滤器
1.布隆过滤器的作用
主要作用: 防止缓存穿透(当redis上不存在的时候,当请求来的时候就会去查询数据库,数量小的时候没事,但是当数据量大的时候,那此时大量的请求压在数据库可能导致数据库崩溃),黑名单设置(垃圾邮件识别)。
2. 构成
布隆过滤器的组成就是一个bit数组 和 多个hash函数构成,当数据来的时候通过hash函数多次求的值,在bit数组上对应位置置为1,就代表特定数据。这时候也就有了问题看图
比如说A通过hash函数将0,1,2,3,4五个位置的0置为1,B则将2,3,4,6,7置为1 ,这时候可以注意到重复部分2,3,4这时候就引入一个新的概念,就是布隆过滤器不要删除,因为一个bit为并不是独占的,可能多个占用。此时还有一个可能就是例如C正好经过hash时候会映射的位置时1,2,3,4,6但是此时布隆过滤器中并未存储C的相关信息,但是此时这些位置都是1 ,也就产生了误判,也就是布隆过滤器存在误判
总结:一个元素判断结果为没有时则一定没有, 如果判断结果为存在的时候元素不一定存在。
优点:高效地插入和查询,占用空间少
缺点:不能删除元素。因为删掉元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的,你删除一个元素的同时可能也把其它的删除了。
存在误判不同的数据可能出来相同的hash值
代码演示
1)使用谷歌提供的方式
<!--guava Google 开源的 Guava 中自带的布隆过滤器-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.util.ArrayList;
import java.util.List;
public class GuavaBloomfilterDemo
{
public static final int _1W = 10000;
//布隆过滤器里预计要插入多少数据
public static int size = 100 * _1W;
//误判率,它越小误判的个数也就越少(思考,是不是可以设置的无限小,没有误判岂不更好,开销会越来越大,建立的bit数组大小)
public static double fpp = 0.01; //默认值0.03
public void bloomFilter()
{
// 创建布隆过滤器对象
BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), 100);
// 判断指定元素是否存在
System.out.println(filter.mightContain(1));
System.out.println(filter.mightContain(2));
// 将元素添加进布隆过滤器
filter.put(1);
filter.put(2);
System.out.println(filter.mightContain(1));
System.out.println(filter.mightContain(2));
}
/**
* 误判率演示+源码分析
*/
public void bloomFilter2()
{
// 构建布隆过滤器
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(),size,fpp);
//1 先往布隆过滤器里面插入100万的样本数据
for (int i = 0; i < size; i++) {
bloomFilter.put(i);
}
/* List<Integer> listSample = new ArrayList<>(size);
//2 这100万的样本数据,是否都在布隆过滤器里面存在?
for (int i = 0; i < size; i++)
{
if (bloomFilter.mightContain(i)) {
listSample.add(i);
continue;
}
}
System.out.println("存在的数量:" + listSample.size());*/
//3 故意取10万个不在过滤器里的值,看看有多少个会被认为在过滤器里,误判率演示
List<Integer> list = new ArrayList<>(10 * _1W);
for (int i = size+1; i < size + 100000; i++)
{
if (bloomFilter.mightContain(i)) {
System.out.println(i+"\t"+"被误判了.");
list.add(i);
}
}
System.out.println("误判的数量:" + list.size());
}
}
2)使用jrebloom布隆过滤器
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>2.1.0</version>
</dependency>
import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedissonBloomFilterDemo
{
public static final int _1W = 10000;
//布隆过滤器里预计要插入多少数据
public static int size = 100 * _1W;
//误判率,它越小误判的个数也就越少
public static double fpp = 0.03;
static RedissonClient redissonClient = null;//jedis
static RBloomFilter rBloomFilter = null;//redis版内置的布隆过滤器
static
{
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);
//构造redisson
redissonClient = Redisson.create(config);
//通过redisson构造rBloomFilter
rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter",new StringCodec());
rBloomFilter.tryInit(size,fpp);
// 1测试 布隆过滤器有+redis有
//rBloomFilter.add("10086");
//redissonClient.getBucket("10086",new StringCodec()).set("chinamobile10086");
// 2测试 布隆过滤器有+redis无
//rBloomFilter.add("10087");
//3 测试 ,布隆过滤器无+redis无
}
private static String getPhoneListById(String IDNumber)
{
String result = null;
if (IDNumber == null) {
return null;
}
//1 先去布隆过滤器里面查询
if (rBloomFilter.contains(IDNumber)) {
//2 布隆过滤器里有,再去redis里面查询
RBucket<String> rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
result = rBucket.get();
if(result != null)
{
return "i come from redis: "+result;
}else{
result = getPhoneListByMySQL(IDNumber);
if (result == null) {
return null;
}
// 重新将数据更新回redis
redissonClient.getBucket(IDNumber, new StringCodec()).set(result);
}
return "i come from mysql: "+result;
}
return result;
}
private static String getPhoneListByMySQL(String IDNumber)
{
return "chinamobile"+IDNumber;
}
public static void main(String[] args)
{
//String phoneListById = getPhoneListById("10086");
//String phoneListById = getPhoneListById("10087"); //请测试执行2次
String phoneListById = getPhoneListById("10088");
System.out.println("------查询出来的结果: "+phoneListById);
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
redissonClient.shutdown();
}
}