缓存使用常识

        缓存与数据库相结使用时,一般的使用流程如下: 

一、缓存击穿:

        是指缓存中某个key的缓存非常热点,在不停的扛着大并发,在某一时刻请求用户特别多,但是从缓存都没取到数据(比如缓存失效),只能去数据库去查数据,引起数据库压力瞬间增大,就像在一个屏障上凿开了一个洞。解决方案为:

        1、加锁:

        ①分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端数据库,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

        ②本地锁:通过本地锁,限制只有一个线程去数据库中查询数据,而其他线程只需等待,等前面的线程查询到数据后再访问缓存。但是这种方法只能限制本地服务节点只有一个线程去数据库中查询,如果服务有多个节点,则还会有其他节点上的线程查询数据库,也就是说在节点数量较多的情况下并没有完全解决缓存并发的问题。

        2、软过期:指对缓存中的数据设置失效时间,但是不使用缓存服务提供的过期时间,而是业务层在数据中存储过期时间信息,由业务程序判断是否过期并更新缓存。当发现数据即将过期时,将缓存的时效延长,程序可以派遣一个线程去数据库中获取最新的数据,而其他线程这时看到过期时间延长了,就会继续使用缓存中的旧数据,等派遣的线程获取最新数据并更新缓存后就可以访问到缓存中的新数据。也可以通过定时任务定期读取数据库的数据更新到缓存,这样应用层就不用关心缓存并发的问题了。

二、缓存穿透:

        是指缓存和数据库中都没有要查的数据,而用户不断发起请求,每次请求都要穿透到后端数据库系统进行查询,使数据库压力过大,甚至使数据库服务被压死。解决方案为:

       1、如果每次使用同一个key进行查询,而每次该key在缓存中不存在。可以将该key的值设置为空值放在缓存中,避免缓存穿透查询数据库;

       2、如果每次使用不同的key进行查询,这些key在缓存中都不存在。可以对key进行校验,使其限定在在缓存中存在的范围内,不在其范围的直接拒绝。

三、缓存雪崩:

        是指缓存服务器重启或者大量不同key值的缓存集中在某一个时间段内失效。这样给后端数据库造成瞬时的负载升高的压力,甚至压垮数据库的情况。解决方案为:

       1、对不同的key使用不同的失效时间,甚至对相同的数据、不同的请求使用不同的失效时间。例如,我们要缓存user数据,会对每个用户的数据设置不同的缓存过期时间,可以定义一个基础时间,假设10秒,然后加上一个两秒以内的随机数,过期时间为10~12秒,就会避免缓存雪崩;

       2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中;

       3、设置热点数据永远不过期;

四、并发问题:

        当一个key过期时,如果访问这个key的请求量较大,多个请求同时发现缓存过期,因此多个请求会同时访问数据库来查询数据,并且回写缓存,这样会造成应用和数据库的负载增加,性能降低,甚至会导致数据库被压死。

        从理论上来讲,给缓存设置过期时间,是保证最终一致性的解决方案。因为对缓存的数据设置了过期时间,缓存的所有数据以数据库为准,对缓存变更只是尽最大努力即可。也就是说数据库更新成功,即使缓存更新失败,那么只要到达缓存过期时间,后面的读请求线程自然会从数据库中读取新值然后更新缓存,虽然牺牲了数据的即时性但提高了缓存和数据库的一致性,但是设置缓存过期时间就会存在缓存击穿、穿透乃至雪崩的问题。

        综上,如果不设置缓存过期时间,缓存击穿、穿透乃至雪崩的问题解决了。但在修改数据时,必须要确保缓存和数据库都能成功修改。但是在高并发的情况下,可能导致修改后缓存和数据库数据不一致。数据变更包括:增、改、删,其中增加和修改(不管是先操作缓存,还是先操作数据库)后,如果缓存和数据库数据不一致(不管是并发高导致的,还是系统问题导致的)都可以重新请求直到缓存和数据库数据一致。但是在删除操作(不考虑逻辑删除情况,因为缓存和数据库都是置状态,就像修改一样可以重新发起请求直到缓存和数据库数据一致为止)时,假设有A、B两个线程,A要清除数据,B以及其他线程高并发请求查询数据,期望将缓存和数据库中指定的数据清除,会出现如下情形:

       1、A操作顺序为先清除缓存,再删除数据库:

              1>在单一数据库的情况下,如果执行的顺序为:

                     ① A:清除缓存

                     ② B:从缓存查数据,发现没有数据

                     ③ B:从数据库中查数据,获取到数据data

                     ④ B:将data写入到缓存

                     ⑤ A:删除数据库中数据data

              2>在数据库是读写分离的数据库集群情况下,如果执行的顺序为:

                     ① A:清除缓存

                     ② A:将数据从主库删除

                     ③ B:从缓存查数据,发现没有数据

                     ④ B:从数据库的从库中查数据,此时还未完成主从同步,所以获取到数据data

                     ⑤ B:将data写入到缓存

                     ⑥ A:完成主从同步,删除从库的数据

       这两种情况下,数据库中已没有数据,但缓存中仍然有data,由于缓存没有设置过期时间,查询的请求线程每次都会去查缓存拿到数据data,而实际上这条数据已经删除,出现缓存和数据库数据不一致。为了避免这种情况发生该怎么办?那就是A线程采用双删策略,也就是B线程从数据库删除完数据之后再对缓存进行一次删除操作。

       我们只考虑异常情况,如果第二次删除缓存失败怎么办?如果对这种情况没做特殊处理,数据库中已经没有这条数据,但缓存中还有这条数据,而此时我们已经不知道缓存中哪条数据要删除却没删除成功(因为数据库的数据已被物理删除,相应的缓存的key值就无法知道)。

       为此,我们应该以缓存数据最后有没有删除成功决定数据库删除是不是要回滚。另外,将数据库设计为逻辑删除,并且缓存的key值可通过数据库数据内容生成固定值,这样即使缓存删除不成功,也能从数据库中的数据计算出缓存的key值进行人工删除。还可以把要删除的缓存的key值事先存入数据库,如果缓存删除成功就将这条记录清除,否则保留这条记录,通过程序或手工根据这些key值清除缓存,或参考情形2【先修改数据库,再清除缓存】。

       2、A操作顺序为先删除数据库,再清除缓存:

       由于缓存不设置过期时间,而且也不先清缓存,所以缓存中永远有数据,这样就不会触发B线程从数据库查数据并设置到缓存的行为,也就不存在缓存和数据库数据不一致的现象,除非是由于删除数据库数据成功,但是在清除缓存时失败导致的缓存和数据库数据不一致。此时与情形1中的双删策略第二次删除缓存失败一样,也需要做一些特殊的补救措施。

发布了108 篇原创文章 · 获赞 31 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/lzghxjt/article/details/87363864
今日推荐