干货分享 | 什么是缓存击穿、雪崩、穿透

小豆丁 8月前 ⋅ 271 阅读

随着互联网的越来越普及,用户越来越多,系统性能瓶颈成了越来越热门的话题。要解决性能问题的技术手段有很多,比如:缓存、CDN加速、页面静态化、集群、分布式、异步等。 缓存通常被作为首先技术方案,简单而且提升效果明显,它能够将速度提升100倍。那么问题来了,缓存为啥会怎么快呢? 因为传统的数据库操作是基于磁盘的,而缓存是基于内存的,内存操作和磁盘操作的速度根本不是一个数量级的。目前市面上主流的缓存有:redis 和 memcache,这两个都是基于内存的缓存技术,二者的区别我在这里暂时不讲。使用缓存的伪代码一般如下:


String order = redisClient.get(key);
if(order != null) {
   return order;
}
order = db.get(key);
redisClient.put(key,order);
redisClient.expire(key,3000);
return order;

根据key获取数据,先从缓存中查一下有没有,如果有则直接返回。如果没有,再从数据库中查到数据,然后将数据放入缓存中,并且给当前key设置一个失效时间,下次再用同样的key来请求数据时,就能够直接从缓存中查询到并返回,减少请求数据库的频次,提升性能,因为数据库连接是稀有资源。 那么问题又来了,为啥要设置失效时间,不设置不行吗? 著名的2/8原则告诉我们,经常访问的数据集中在20%,而另外的80%属于不常用数据。我们都知道内存相当于磁盘来说价格是比较昂贵的,不信你买个500G的硬盘 和 一个 500G的内存试试。既然这么贵,我们应该节约使用,所以才会有设置失效时间这种策略,一旦检测到某个key超过了失效时间,就会将该key从缓存中删除,可以节约内存。

还有个问题:如果在某个key失效的时候,有大量的请求一起过来会怎么样? 这就是我今天要给大家讲的:击穿。 大量的请求访问同一个key,刚好那个key失效了,那么同一时间所有的请求,都会穿过缓存,直接请求数据库,此时的数据库有可能因为无法扛着这么大的并发,直接挂了。

再问一下:如果大量的请求访问多个key,刚好key同时失效了会怎么样? 这就是我今天要给大家讲的:雪崩。 https://mmbiz.qpic.cn/mmbiz_png/uL371281oDEltuPhDALy7ELR10AicRg8Yw0v5LHpSiaJ35oGOticpzUuMxjgkrhNuib0MZoEfTciaxZWRR0ZcmzZlGw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

雪崩比上面的击穿更严重,击穿只是一个key失效了,大量请求直接访问数据库都有可能把数据库搞挂,更何况大量的key同时失效的场景,数据库面临的压力更大,更有可能挂掉。

接下来的问题:如果大量的用户请求缓存中不存在的key又会怎么样? 这就是我今天要给大家讲的:穿透。 https://mmbiz.qpic.cn/mmbiz_png/uL371281oDEltuPhDALy7ELR10AicRg8YXV6TWXHkJJrQZDvYvIMiaoghLsFMFBTsWicc0DzbxD8oZmtBiaGyjPia3w/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

有大量的请求访问时,只有少部分的key在缓存中存在,而有大量的key不存在,这样请求也会直接访问到数据库,也会导致数据库扛不住压力而挂掉。这种情况往往是黑客伪造请求,发起的恶意攻击。

那么,这些问题有没有解决办法呢? 首先,击穿的解决办法-加锁。

伪代码如下:

String order = redisClient.get(key);
if(order != null) {
   return order;
}
lock() {
   String order = redisClient.get(key);
   if(order != null) {
      return order;
   }
  order = db.get(key);
  redisClient.put(key,order);
  redisClient.expire(key,3000);
}
return order;

如果根据key从缓存中查询不到数据,需要从数据库中查询数据的时候,加一把锁,保证同一时间只有一个线程可以查询数据库,然后把查询出来的结果放回到缓存中。这样其他的线程再用相同的key查询时,就可以直接从缓存中查到数据。这样就能够极大的减少数据库的访问频次。

其次,雪崩的解决办法- 加锁 + key设置不同的失效时间。 加锁的伪代码跟上面是一样的我就不写了。 雪崩还有一个必要条件就是在同一时间,有大量的key同时失效。我们只要保证不会出现同一时间有大量的key同时失效就可以了,每个key设置不同的失效时间就能解决问题。

最后,穿透的解决办法- 业务规则过滤 + 布隆过滤器 业务规则过滤 可以校验 key的长度或者比如前缀SD开头的等,过滤一批非法数据。

接下来看看布隆过滤器: https://mmbiz.qpic.cn/mmbiz_png/uL371281oDEltuPhDALy7ELR10AicRg8Ynic0gsGHCa8cmbX0aOfNInpUajVlDToZstA3Xoib9pqicCJbpGoAFk5ww/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1

布隆过滤器中会初始化数据库中key的标识。如果有大量请求访问不存在的key时,先通过布隆过滤器检查一下key在数据库中是否存在,如果存在才允许访问数据库。如果不存在,则直接返回,这样就可以过滤掉大量的非法请求。

https://mmbiz.qpic.cn/mmbiz_png/uL371281oDEltuPhDALy7ELR10AicRg8YjGAPyBaQGnG4Ff2bTSsHFMHWTeyEiabErQsbBwV0dXaibfHe6LIZcPWw/640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1


全部评论: 0

    我有话说: