缓存相信好多项目大家都用到过,并发下缓存的问题也是比较多的,前几天有个朋友还问我知道什么是缓存穿透吗?我一脸茫然回答道:不知道。然后正好有幸看到某个学堂,讲解到了这个东西。特此来记录下。
一、 什么是缓存穿透
关于缓存衍生了好多问题,这里暂且不一一介绍了,老实说我也不是很清楚,但是接下来肯定会慢慢地进行了解记录学习。
至于缓存是什么,这个这次就先不介绍了,朋友们自行百度学习吧,网上教程挺多的。关键变成自己的才是最重要的,希望以后大家都是大牛。好了言归正传。
什么是缓存穿透呢?
大家都从网上买过东西,我们买好了以后由物流进行发货,物流商会提供一个快递单号给我进行查询,我们根据快递单号就可以把物流信息查询出来,那么我们后台怎么来做呢,一般来说都是放到数据库里面的,但是为了提高效率,避免平凡跟我们数据库进行IO操作,我们放到缓存中去,直接从缓存中取得。所以流程是这样:先从缓存中看看有没有,没有再去数据库中查询,查询出来返回,这是我们有数据的情况。那如果我们没有数据的时候,我们是不是每次都会去数据库,但是返回null的数据,但是这个查询是不是也要耗时呢?如果大批量的查询呢?那是不是就会影响效率呢。这样引出缓存穿透的问题。
缓存穿透:就是指查询一个不存在的数据,这样的请求到数据库查询,失去了缓存的意义。再流量大时,可能DB就挂掉了。
****二、怎么解决这个问题呢
我们可以再访问缓存之前,加上一张网,来过滤不在我们数据库中的数据。也就是说:怎么判断当前单号是否在上百万数据中或者千万级别的数据中呢。我们使用一个布隆过滤器来解决这个问题。
引入jia包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
注意:版本太低可能没有那个类
来看下这个布隆过滤器,底层是算法,有兴趣的朋友自行研究下吧。大致讲下这个类。
它里面有两个重要的属性:随机函数方法和二进制向量
一个快递单号过来以后,通过随机函数方法,我们假设随机函数方法有:f1,f2,f3,首先通过f1算法,然后在二进制向量中存储了某个地方,然后经过f2算法,再二进制向量中,存储了某个地方,再经过f3算法,把这个快递单号的信息存在了二进制向量中。这是存,来看下判断是否有这个值。一个快递单号经过f1.f2.f3看看二进制数组里面返回的是不是1,0代表没存。如果都返回1说明有这个号,我们从缓存取,当然需要提前把数据缓存到缓存中去。当然这个东西有优点也就有缺点,缺点是存在误判。所有没有就去数据库取一下。
代码
public class BloomFilterDemo {
private static final int insertions = 1000000;//100W
@Test
public void bfTest() {
//初始化一个存储String数组的布隆过滤器,初始化大小100W
BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions);
//初始化一个存储String数组的set,初始化大小100W
Set<String> set = new HashSet<String>(insertions);
//初始化一个存储String数组的list,初始化大小100W
List<String> list = new ArrayList<String>(insertions);
//向三个初始化容器放入100W的随机并且唯一的数据
for(int i=0;i<insertions;i++) {
String uuid = UUID.randomUUID().toString();
bf.put(uuid);
set.add(uuid);
list.add(uuid);
}
//布隆过滤器错误判断次数
int wrong = 0;
//布隆过滤器正确判断次数
int right = 0;
//大街上找10000个人
for(int i=0;i<10000;i++) {
String test = i % 100 ==0?list.get(i/100):UUID.randomUUID().toString();//按照一定比例选择bf中存在的值
if(bf.mightContain(test)) {
if(set.contains(test)) {
right ++;
}else {
wrong ++;
}
}
}
System.out.println("==========rigth========="+right);
System.out.println("==========wrong========="+wrong);
}
}
结果:==========rigth=========100 ==========wrong=========289
为啥有差不多300误差呢,因为我们的bloomFilter默认是3%。那我们可以控制这个计算率,现在是千分之一进行计算,来看下结果
BloomFilter<String> bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions,0.001);
==========rigth=========100
==========wrong=========18
至于这个算法大家可以看下底层怎么写。