00:00
好,前面呢,我们使用缓存改造了一下我们的业务逻辑,那这个使用起来逻辑也非常简单,那想要做复杂业务逻辑之前,那先来判断一下缓存里边有没有之前我们已经做好的结果,如果我们缓存里边有,那就直接返回我们缓存里边的,如果我们缓存里边没有,那才需要执行我们真正的业务逻辑,查数据库,组装数据等等,那查到以后呢,我们接下来把它们再放入缓存,返回这个结果,那下一次我们进来缓存呢,就会有这个结果。我们就无需再来进行我们复杂业务逻辑了,它就极大的提升了我们的吞吐量,提升我们系统性能,但是在我们高并发分布式系统下,就这么一个简单的缓存逻辑,它暗藏了很多的玄机,如果我们使用不好,还会引申出非常多的问题,那么就来给大家分析一下咱我们分布式系统下,我们这样来使用缓存有没有什么问题?好来看一下我们这一块的PPT,那首先来看一下在我们高并发系统下,我们缓存失效引申的几个问题,所谓的缓存失效指的就是呢,我们缓存没有命中,没有查到数据,我们缓存呢,没有使用到,我们就可以简称为缓存时效,那一旦我们缓存里边没有命中,那可能就会出现如下里边的一些问题,比如第一个问题叫缓存穿透,来想象一种场景,假设呢,我们现在高并发,100万的并发全部进来,我们现在要查11号商品,我们这个商品数据假设我们。
01:34
的缓存中没有,我们来看我们这一块的逻辑好,假设100万呢都进来,我们从缓存中获取11号的商品数据,缓存中呢,没有没有我们这个100万同时判断都是没有,然后他们都进来,然后呢,全部去来查数据库,相当于100万的并发全是查数据库,11号商品的查了以后呢,范围结果,然后再来返回,那相当于我们缓存就没有用上了,直接来到了我们数据库来进行查询。
02:06
那瞬时的压力就会导致我们这个数据库承受不了,导致我们数据库停止,服务崩溃等等,特别如果别人利用我们这个漏洞假设呢,我们这个11号商品,只要有人查过,那还好,在缓存里边呢,一直有假设我们商品没有一个1万号商品,那么系统的商品呢,现在就到7000号,没有1万号商品,接下来有一个人专门利用这些恶意工具查询1万号商品,然后大并发给我们请求发过来,我们据来查缓存,我们来看一下我们现在写的这个结果,好,我们查询呢,1万号商品,我们数据从缓存中拿到的是,那是空的,我们判断呢,这是空的,然后我们都去查数据库,这样相当于100万全进来,全查数据库,这就是我们说的缓存穿透问题。也就是我们只要查询一个一定不存在的数据,就会出现缓存穿透,穿透的原因就是我们没有将我们的空结果也要写入缓存,假设我们这个1万号的商品,即使是空的,我们数据库查来是空的,我们给缓存里边也放一个标志位,比如是零也好,我们放了一个数据,那接下来呢,下次来查询,从缓存中就能拿到这个数据,哪怕是一个零,而不是拿到一个空,这样呢,只要拿到了,我们就不会来到我们数据库,就不会产生我们缓存穿透的问题,我们也不用担心这些恶意攻击。所以呢,要解决这个问题,我们的做法就是把我们的空结果进行缓存,但如果我们一直把这个空结果进行缓存,那未来即使有这个数据,我们缓存里边呢,那都存的是没有,所以呢,我们可以在缓存数据的时候给我们空结果加一个短暂的过期时间,比如三分钟五分钟过期那。
03:55
过期以后呢,下次查缓存中没有,那他就会真正的再去查一次数据库,但是不管怎样,数据库此次有还是没有,我们都应该将空结果继续缓存,这是我们说的第一个问题,将缓存穿透,我们一直来查询一个不存在的结果,导致缓存一直不命中,全部来查数据库。
04:16
就会导致数据库的瞬时压力过大,导致我们数据库崩溃。好,接下来我们再来看第二个问题,叫缓存雪崩,缓存穿透指的是我们查一个不存在的数据啊,缓存雪崩指的是什么呢?假设我们给缓存里边放了非常多的数据,这有菜单数据,有商品数据,有我们品牌数据等等等等,包括商品数据呢,从一到7000号商品都有,但是我们在放数据的时候,我们给每一个数据呢,都设置了相同的过期时间,比如举一个例子,我们放的时候呢,正好这1万个数据全部都是同时放进去的。而他们拥有相同的过时时间,那等到某一时刻以后,像这1万个数据全体都在缓存中没有了,过期了,Red给它自动删掉了,那这样我们100万的并发进来,巧了,这100万都是来查这1万个数据的。
05:12
那这1万个数据呢,在缓存中都没有,他们又进来去查数据库了,所以这就导致我们说的缓存雪崩问题,雪崩指的是我们所有的存来的数据大面积失效,所以呢,要解决我们这个问题,那我们最好的办法就是我们在从每一个数据的时候,他们的过期时间最好呢来加一个随机的值,这样保证我们缓存里边的所有数据不会在某一时刻集体失效,导致缓存不命中,又把请求放给数据库,所以这是我们说的缓存雪崩问题。雪崩指的是大面积K同时失效,而穿透指的是查询一个永不存在的数据。接下来们再来看最后一个问题,我们叫缓存击穿,那区别于雪崩,我们雪崩呢是大面积同时失效,但是呢,我们现在来考虑这一个问题,单看这个字,什么叫击穿,比如我们枪法很准,我们瞄着墙的某一个圆心,我们一直呢给它进行射击,那最终呢,这一块就会被打穿。
06:19
那么击穿呢,指的也就是这样,我们会访问一个热点的K,什么叫热点的K,比如我们这个手机iPhone刚发布新品,我们iPhone手机上架了以后,每天大家光查iPhone手机,比如这是一号的商品,要面对百万的查询,但是呢,我们来存iPhone手机的时候,我们给缓存里边设置iPhone的过期时间,比如我们是一天正好呢,到晚上没人的时候,IPhone手机的整个信息缓存失效了,然后到第二天一大早起来,突然高峰期流量100万请求全进来,来查询iPhone,但此时呢,IPhone已经失效了,缓存中没有,现在100万的请求全部又进来放给我们数据库了,那这样呢,又会导致我们缓存被击穿,我们全部压力被数据库承担,我们数据库呢最终崩溃,服务无响应。
07:10
所以呢,我们缓存击穿指的是某一个K失效,但是呢,这个K是一个高频热点数据,我们每天都有很大的流量去来访问,而且呢,失效的这一刻正好是大量请求同时进来,要说失效的这一刻进来了十来个请求,无所谓,放过去哪怕查了十来遍也都没事,但是呢,失效的这一刻正好有百万并发全部进来,那么这个就缓存就被击穿了,所以我们要解决这个问题,想到的方式就是加锁,既然大家都是查一号商品,我们100万的并发进来,好都等一等,我们加上一把锁,只让一个人去来查数据库,查到以后呢,把这个结果放到缓存,他查完了以后释放锁,别人拿到锁以后呢。
08:00
继续进行,但别人拿到锁不继续来查数据库,别人拿到锁以后,先来看我们缓存中还有没有了,如果有了就没必要去来查数据库了,这样呢,最终我们即使百万并发进来,我们只有一个人去来查数据库,这就很合适了。这就是我们说的缓存的三个问题,回顾一遍,穿透指的就是我们查询一个永不存在的数据,我们一定都要过DB再来查询,这就是穿透解决的方案,就是我们将空结果也缓存进来。即使数据库查到为空的,下次来到缓存,缓存里边能拿到这个数据,拿到这个数据呢,不是,那它可能是一个另外的一个标志位,零也好,一也好,或者是处也好,False也好,但是我们没有拿到now数据,所以我们就不用去数据库进行查询,我们请求呢全部就放不进来了,这是我们第一个缓存穿透下来,第二个缓存雪崩指的是我们大面积K同时失效,然后高并发进来,都是来查询这些请求,缓存里边没有同时放进来。
09:05
我们再去来查询DB,我们DB压力过大,导致我们服务无响应,那我们这个的解决方案就是我们缓存每一个数据的时候,给他过期时间,可以加上一些随机值等等。接下来最后一个就是缓存击穿,击穿指的就是我们某一个单点K被高频访问,高频访问的前一刻正好它失效了,百万并发全放进来给数据库,数据库就受不了,我们的解决方案就是枷锁。所以啊,要让我们缓存的这段代码完美运行,我们还得加上以下的业务逻辑好,首先第一个我们得加上空结果缓存,这个空结果缓存就是为了解决我们这个缓存的穿透问题,来写一下缓存穿透。第二个我们还得设置过期时间,而且呢,这个过期时间还是要能加随机时的,我们这个过期时间加随机值,这个呢是来解决我们这个缓存雪崩问题,然后呢,最后我们还要加锁它来解决我们这个缓存击穿问题,如果真的到了查询数据库这一步,我们就得来进行加锁,不能让所有人都去查,前面这两个都好做,比如我们在这一块判断,如果我们拿到的这个结果是空的,我们给缓存里边,比如我不放这个序列化后的结果了,我们放一个零或者什么,但是如果我们拿到了真正的值,那么就放进去,然后第二个也好解决,那么加一个过期时间,我们在这操作的时候呢,我们可以来看一下,我们在这set的时候,我们除了能在这set kv,我们这还能指定过期时间。
10:52
比如我们来写上K呢,就是这个catalog,杰森值呢,就是我们这个S,然后过期时间我们可以指定有多长时间,比如我想指定一天,那我写一个一,然后呢,接下来时间单位来写一个time unit,然后我们写一个days,好,那相当于一天以后再过期。
11:13
前两个都好解决,但就是加锁这一步,如果锁加不好,又会出现很多问题,那下一节课就来分析我们该如何加速才能把我们这些问题全部解决。
我来说两句