项目中为什么使用缓存:
- 加快系统的响应速度,提高系统吞吐量
- 通过缓存可以减少瞬间大量的请求操作数据库,减少数据库IO;避免数据库系统瘫痪,而引起系统不可用
一、缓存穿透:
概念:缓存key对应的数据在缓存中不存在,并且在数据中也不存在;每次请求都会到达数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库
解决方法:
1、缓存null对象:直接把数据库查询的数据放入redis中,即使他是一个空对象;这样可以解决同一个key反复请求数据的问题
缺点:
- 如果更换key他依然会去查询一次数据库,如果有黑客使用一个随机不存在的key频繁访问依然会影响数据库
- 缓存空对象会占用内存
2、布隆过滤器
布隆过滤器:(Bloom Filter)是由布隆在1970年提出的。它实际上是由一个很长的二进制向量和一系列随机映射函数组成,布隆过滤器可以用于检索一个元素是否在一个集合中
算法:
1. 根据一定的规则(误判率)计算出一个长度为n比特的数组,每个比特位初始化为0
2.根据一定的规则(误判率)计算需要k个hash函数,每个函数可以把key散列成为1个整数
3. 某个key加入集合时,用k个hash函数计算出k个散列值,把每个散列值映射到bit数组对应的bit位,并设置为1
4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中
优点:使用bit位表示元素是否存在,占用空间小;
缺点:
- 存在误识别率:误判一个元素存在集合中,而实际上他并不存在(不会存在漏报:一个元素不存在数据集中,那么布隆过滤器一定不会判断他存在)
- 删除元素困难:因为同一个bit位可能也标识其他元素;无法删除元素带来的问题是,如果我们把数据集的元素删除了,而布隆过滤器会判断他存在,这样请求依然会进入数据库,如果删除的数据较多我们应该重写布隆过滤器中的数据
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<optional>true</optional>
</dependency>
private static int size = 1000000;//预计要插入多少数据
private static double fpp = 0.01;//期望的误判率
private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
public static void main(String[] args) {
//插入数据
for (int i = 0; i < size; i++) {
bloomFilter.put(i);
}
int count = 0;
for (int i = size; i < size*2; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "误判了");
}
}
System.out.println("总共的误判数:" + count); //总共的误判数:10314
}
按照我们程序的执行结果可得出误判率=10314/1000000=0.010314(判断元素是否存在,应该是1000000个不存在) ,和我们定义的期望误差率0.01 接近
二、缓存击穿
概念:缓存key对应的数据存在,但在缓存中已经过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回写缓存,这个时候大并发的请求可能会瞬间把数据库压垮;例如redis中key的过期时间;该类数据一般是热点数据
解决方案:
- 如果是分布式系统使用分布式锁
- 如果是单机部署JVM锁就可以
伪代码:们需要查缓存两次,这样可以保证在并发场景下,只有第一个线程去查数据库,后面的线程可以用缓存数据
redis.get(key)
lock()
redis.get(key)
select.db
redis.set(key,value)
unlock()
三、缓存雪崩
概念:缓存雪崩是指缓存中的数据大批量到过期时间或者缓存服务器宕机,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方法:
- 缓存服务器:缓存服务器要高可用,例如redis搭建集群或者一主n备
- 过期时间:分散各个key对应的过期时间
- 服务限流或者降级、熔断
四、缓存一致性问题
redis缓存与数据库数据不一致问题
解决方法:
- 延迟双删:休眠时间无法确定,该方法不靠谱
- 使用队列:使用队列可以保证命令的顺序执行,但是影响系统的效率
- canal同步mysql的binlog: