一、什么是缓存
-
缓存实质就是一个临时的存储数据的容器,数据在这个容器中可以提高读取速度。
-
缓存分为硬件缓存和软件缓存,硬件缓存是位于CPU和内存之间的临时存储器,软件缓存又分为内存缓存、数据库缓存、网络缓存
-
使用缓存主要的目的有两个:实现高性能、高并发
-
二、使用缓存潜在的问题
问题一:缓存与数据库的一致性
-
造成缓存与数据库数据不一致的场景是在更新数据的时候发生的
-
方式一:更新数据时先更新数据库中的数据,更新成功之后再删除缓存中对应的数据
-
存在问题:当更新数据库数据成功,更新或删除缓存数据失败时,后续的查询操作取到的数据是旧数据
-
解决方案:先更新数据库,成功后往消息队列发消息,消费到消息后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果
-
-
方式二:更新数据时先删除缓存中的数据,删除成功之后再更新数据库中对应的数据
-
存在问题: 在高并发场景下两个请求几乎同时到达。请求一是更新操作,先删除了缓存,正在查询数据库,请求二是查询操作,在请求一查询数据库的时候请求二查询缓存没有找到数据,就去数据库查询此时请求一还没更新完毕,请求二查到了数据并将旧数据放到了缓存中,后续的查询操作查询到的数据都是旧数据
-
解决方案: 延迟双删,为了避免更新数据库数据的时候,其他线程从缓存中读取不到数据而从数据库取到旧数据更新缓存,就在更新完数据库之后,再 Sleep 一段时间,然后判断缓存中是否有数据有就再次将缓存中的数据删除。
-
问题二:缓存穿透
-
场景:请求缓存中不存在的数据,从而去数据库中查,数据库中也没有,所以无法加到缓存,当这类请求高并发时将导致数据库崩溃
-
解决方案:
-
利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
-
数据库没查到数据,也往缓存中写入一个空值,但是设置失效时间短一点,防止恶意
-
问题三:缓存击穿
-
缓存击穿是指缓存的热点数据刚好过期时(失效),大量并发查询请求到达,此时读缓存没读到数据,应用就直接在数据库中查询数据,引起数据库压力瞬间增大,造成过大压力 ,可能导致数据库崩溃。
-
解决方案:
-
当多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它 ,其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存
-
可以将爆款的缓存失效时间设置为永久。
-
问题四:缓存雪崩
-
缓存雪崩是出现的场景一般是指正常使用的缓存组件突然发生不可用情况,导致系统处理能力大幅度降低此时有需要处理大量请求时导致数据库压力过大进而导致系统的崩溃。 导致缓存突然不可用的情况有很多比如上述提到缓存热点数据同时失效、缓存节点故障等等
-
解决方案: 缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。 设置不同的过期时间,让缓存失效的时间点尽量均匀。 设置热点数据永远不过期。
三、如何测试缓存
-
在一个系统中如果使用了缓存方案就不可避免存在上述问题。 测试前需要清楚知道被测系统采用的缓存实现方案以及更新策略,根据方案及策略的特点编写有针对性测试用例。
-
主要测试点:
-
出现数据不一致时对业务的影响
-
是否考虑到系统在发生缓存击穿场景下处理方案
-
是否考虑到了系统在发生缓存穿透情况下的处理方案
-
当系统发生缓存组件服务异常时或是缓存全部失效时在正常的业务量下系统是否撑得住
-
-
缓存测试场景参考: