目录
1 缓存使用场景
有些数据查询频率很高的时候,我们会将数据存入到缓存,用户每次查询直接查询缓存即可,从而提高用户访问数据的效率。
比如获取用户为 lisi 的抢红包记录,此时如果每次查询数据库效率都很低,我们可以第1次从数据库查询 lisi 最近的前10条抢红包记录,然后将记录存入到Redis缓存,下次直接查询Redis缓存即可。
每次用户抢红包,谁抢到了红包,我们会将抢到红包的用户信息按照抢红包的金额大小的前100名用户信息公示出去,这里也可以采用这种方式来做。
当然,也不是所有数据都适合做缓存,需要根据数据特点来决定,如下图:
2 缓存穿透介绍
上面查询最近红包记录提升用户访问效率,这种操作是一种正常操作,但也存在一些非正常操作,比如 wangwu 没有抢到红包,但用户恶意频繁去查询抢红包记录,此时Redis缓存中将一直没有数据,每次都会查询数据库,这种现象叫缓存穿透。
缓存穿透该如何解决呢?我们提供这一种思路,如下图所示:
3 穿透问题解决
4 布隆过滤器
防止缓存穿透,有多种方案,上面所实现的是一种最简单的方案
,也有其他方案,比如布隆过滤器也是缓存穿透方案之一。布隆过滤器主要是解决大规模数据下不需要精确过滤的业务场景,如检查垃圾邮件地址,爬虫URL地址去重,解决缓存穿透问题等。
布隆过滤器:在一个存在一定数量的集合中过滤一个对应的数据,判断该数据是否在该集合中。
4.1 原理
布隆过滤器的巨大用处就是,能够迅速判断一个元素是否在一个集合中。因此他有如下三个使用场景:
- 网页爬虫对
URL的去重
,避免爬取相同的URL地址
- 反垃圾邮件,从数十亿个垃圾邮件列表中
判断某邮箱是否垃圾邮箱
(同理,垃圾短信) - 缓存穿透,将所有
可能存在的数据缓存放到布隆过滤器中
,当访问不存在的缓存时迅速返回避免缓存及DB挂掉。
我们来谈谈布隆过滤器的原理
其内部维护一个全为0的bit数组,需要说明的是,布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。
假设,根据误判率,我们生成一个10位的bit数组,以及2个hash函数((f_1,f_2)),如下图所示(生成的数组的位数和hash函数的数量,我们不用去关心是如何生成的,有数学论文进行过专业的证明)。
假设输入集合为((N_1,N_2)),经过计算(f_1(N_1))得到的数值得为2,(f_2(N_1))得到的数值为5,则将数组下标为2和下表为5的位置置为1,如下图所示
同理,经过计算(f_1(N_2))得到的数值得为3,(f_2(N_2))得到的数值为6,则将数组下标为3和下表为6的位置置为1,如下图所示
这个时候,我们有第三个数(N_3),我们判断(N_3)在不在集合((N_1,N_2))中,就进行(f_1(N_3),f_2(N_3))的计算 - 若值恰巧都位于上图的红色位置中,我们则认为,(N_3)在集合((N_1,N_2))中
- 若值有一个不位于上图的红色位置中,我们则认为,(N_3)不在集合((N_1,N_2))中
以上就是布隆过滤器的计算原理。
4.2 布隆过滤器案例
引入依赖包
<!--google的布隆过滤器-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>22.0</version>
</dependency>
编写测试类
public class BloomFilterTest {
private static int size = 1000000;
//Google的布隆过滤器
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, 0.0001);
public static void main(String[] args) {
//放一百万个key到布隆过滤器中 1000000
for (int i = 0; i < size; i++) {
bloomFilter.put(i);
}
List<Integer> list = new ArrayList<Integer>(1000);
//取10000个不在过滤器里的值,看看有多少个会被认为在过滤器里
for (int i = size + 10000; i < size + 20000; i++) {
if (bloomFilter.mightContain(i)) {
list.add(i);
}
}
System.out.println("误判的数量:" + list.size());
}
}
我们可以发现有330个被误判,误判的概率为0.03
,源码中也有说明。
这里的误判概率是可以调整的,每次创建 BloomFilter 的时候,指定误判概率值即可,这个值必须大于0。
优点
- 思路简单
- 保证一致性
-
性能强
缺点 - 代码复杂度增大
- 需要
另外维护一个集合来存放缓存的Key
- 布隆过滤器不支持
删值操作