高并发的场景有三大利器:缓存,限流和降级。
缓存一般是用来降低数据库压力的。使用缓存的场景一般是:先去请求缓存,如果有,直接返回结果;如果没有,查询db,将结果更新到缓存中,然后返回结果。
下边我们讨论几个缓存失效的场景:
缓存穿透
缓存穿透指的是访问不存在的数据。由于数据不存在,请求先访问cache,没有结果,再访问db。每次请求都会落到db上,如果这样的请求过多(比如恶意请求),就会导致db挂掉。
解决思路:
1.将这些不存在的数据也放入缓存,缓存一个null值,表示不存在。
缺点:如果这种不存在的数据比较多,会导致缓存大量的不存在的结果,浪费缓存空间。
2.使用bloomfilter
将全部的数据hash到bloomfilter中。查询时先走bf,如果不存在,直接返回null;如果存在,再去采用1中的方案。
如果布隆过滤器中不存在某数据,说明该数据一定不存在;如果布隆过滤器中存在该数据,只能说明该数据有可能存在。即布隆只能证明没有,不能证明有。
缓存击穿
缓存击穿指的是某一时刻,大量请求同时查询某个key,而这个key恰好在cache中失效了或者不存在,那么这些请求将会同时打到db上,形成缓存击穿。
解决思路:
1.加互斥锁
伪代码如下:
String value = getFromRedis(key);
if(value == null) {
synchronized(key) {
if ((value = getFromRedis(key)) == null) {
value = getFromDb(key);
updateRedis(key, value);
}
}
}
return value;
通过加锁的方式保证在某个key缓存不存在的情况下,只有一个请求真正的去查询db,并将结果放入缓存。其他的请求还是从缓存中取数据。
缓存雪崩
缓存雪崩指的是大面积的缓存失效,导致请求打到db上,使得db挂掉。
造成缓存大面积失效的原因有:
- redis挂掉
- 大量的热点数据同一时间过期
针对一,我们可以给redis集群采用高可用的方案,保证一个redis master挂掉,其他master还可以工作,且挂掉的master的slave自动升级为新的master提供服务(哨兵的自动故障转移)。
针对二,我们可以给热点数据过期加一个合理的随机数值,保证热点数据不会同一时刻一起失效,而是均匀分布。
三,除此之外,我们还可以采用限流+降级的措施,保证db不会被打挂。一二只能尽可能减少缓存雪崩的几率,一旦发生,还可以采用本措施。