持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
一、背景
缓存是日常工作中最常使用的性能优化手段。
但是上了缓存,服务就一定稳定了吗,有哪些要注意的点?本篇文章的目的,是分享缓存系统设计过程中要注意的一些点。
关键字:缓存系统设计、缓存雪崩、缓存穿透、缓存击穿、Thundering Herd、Dog Pile Effect
二、缓存雪崩
缓存空间往往是有限的,为了保证不挤爆缓存空间,往往都会给缓存设置一 个expire 有效期。
如果大量的缓存设置的 expire 值都是一样的,比如说都是一小时。那么一小时后,缓存到时间全部失效,服务就没有了缓存,请求全部会打到底层的数据库,这就是缓存雪崩。
这一问题的解决方案是:设置缓存有效期的时候,加上一个随机的值,让缓存的有效期不一样,这样就不会在同一时间全部失效,造成缓存雪崩的问题。
参考实现如下。我要给缓存设置一个 3600s 的过期时间,为了防止缓存雪崩的问题,在此基础上加了个随机值,这样各个缓存的有效期就会不一样,缓存就不会在同一时间过期了。
public class Main {
public static void main(String[] args) {
System.out.println(getExpire(3600L)); // 6672
System.out.println(getExpire(3600L)); // 5000
System.out.println(getExpire(3600L)); // 6188
System.out.println(getExpire(3600L)); // 7114
}
public static long getExpire(long expire) {
Random r = new Random();
r.nextInt(3600);
return expire + r.nextInt(3600);
}
}
三、缓存穿透
当我们打开某一条微博的时候,打开某一篇新闻的时候。内部的逻辑大概率是根据 id 查询某某实体的详情。
那如果这个 id 不存在,会怎么样呢?程序是先查缓存,缓存不存在,再查数据库,发现数据库也不存在,最后才返回。
乍看也没有什么问题,用 id 查一般也都会很快。但是如果是恶意的、大量的请求呢?请求都堆到数据库,这就不得不防了。
这就是缓存穿透问题,恶意的第三方可以利用这个漏洞攻击我们的系统,给系统带来风险。
解决方案如下。
解决方案1:做参数校验,把非法的 id 的请求全部拦住。像业务上不可能出现负数的id,做个对应的入参校验就行。缺点是可能会有遗漏,很难做到全部拦截。
解决方案2:数据不存在,那就直接把空对象也缓存下来,这样再有不存在的请求,就能访问到缓存了。但是这类请求一多,会占用缓存空间,带来其他的问题,不推荐使用。
解决方案3:布隆过滤器。布隆过滤器是一种基于位图的数据结构,可以快速的判断某个数据是不是不存在,如果布隆过滤器认定不存在,那直接拦截就好。这一方案有一定的复杂性,一是要自制布隆过滤器或者引入开源实现,二是要维护好布隆过滤器,保证不出现误判。 zhuanlan.zhihu.com/p/433689454
四、缓存击穿
和上面的场景类似,当我们打开某一条微博的时候,打开某一篇新闻的时候。
突然吧嗒一声,这条缓存到期失效。试想,如果这是条谁谁谁结婚,谁谁谁离婚的热点微博,瞬间大量请求都因为缓存失效去请求数据库,这对服务是多大的冲击,这就是缓存击穿问题。
解决方案如下。
解决方案1:这类热点数据,不设置缓存有效期 expire,这样缓存就不会因为缓存到期而自动清空。
解决方案2:缓存失效的时候,紧接着会去查询数据库,正是因为查询数据库的请求太多了太导致这个问题,那么在这一步加一个分布式锁即可(分布式锁是否能抗住需要额外关注)。
五、Thundering Herd
这是啃 Ehcache 文档的时候发现的一个和缓存系统设计相关的名词。
从 Introduction 上看就是指高并发场景下,因为缓存穿透或者缓存击穿等原因,导致 DB 负载变高的问题。可以吧 Thundering Herd 当做缓存击穿、缓存穿透的总称,大家以后碰到了可以有个意识。
六、Dog Pile Effect
其实就是缓存击穿的另一种说法,知道就好。
A cache stampede is a type of cascading failure that can occur when massively parallel computing systems with caching mechanisms come under very high load. This behaviour is sometimes also called dog-piling.
七、总结
综上,缓存系统并不是简单的写个 if-else,判断下缓存有没有命中就没有就完事了的。仍然有很多需要额外关注的地方,大家做代码review、系统设计的时候可以额外关注下。
好,就酱,bye bye,下一篇分享下Cache Aside,Cache-As-SOR,缓存一致性相关的问题。欢迎关注 :)
八、引用
- 缓存系列:缓存击穿的解决思路 blog.csdn.net/javahome_la…
- 缓存穿透、缓存击穿、缓存雪崩区别和解决方案 blog.csdn.net/kongtiao5/a…
- 深入浅出分布式系统中的缓存架构 blog.csdn.net/javahome_la…
分享一张图片,梅雨季节难得的晴朗午后,from 煎蛋