前言
之前在针对实习面试的博文中讲到Redis在实际开发中的生产问题,其中缓存穿透、击穿、雪崩在面试中问的最频繁,本文加了图解,希望帮助你更直观的了解缓存击穿😀
(放出之前写的针对实习面试的关于Redis生产问题的博文链接)
Redis生产问题(缓存穿透、击穿、雪崩)——针对实习面试
什么是缓存击穿
缓存击穿(Cache Breakthrough)是指在缓存系统中,一个被频繁访问的数据项(如某个key)在缓存中过期的瞬间,如果有大量的并发请求同时到达,这些请求发现缓存中没有数据,就会直接请求数据库,导致数据库在瞬间承受巨大的访问压力,这可能会使数据库性能下降,甚至崩溃。
缓存击穿是缓存系统中的一种常见问题,它与缓存穿透和缓存雪崩不同:
- 缓存穿透:是指查询数据库中不存在的数据,由于缓存不会保存这些请求的结果,因此每次请求都要到数据库中查询,增加了数据库的压力。
- 缓存雪崩:是指缓存服务器重启或者大量缓存在同一时间过期,所有请求都直接落到数据库上,导致数据库压力过大。
怎么解决缓存击穿?
互斥锁
互斥锁具有强一致,性能差的特点
互斥锁解决缓存击穿的基本原理是在缓存数据失效时,通过互斥锁来保证只有一个线程去数据库加载数据,其他线程则等待该线程加载完数据并更新缓存后再进行访问。这样可以避免多个线程同时对数据库进行访问,导致数据库压力过大的问题。
下面是一个简化的流程图,展示了使用互斥锁解决缓存击穿的过程:
根据以上图示,互斥锁解决缓存击穿的步骤可以详细描述如下:
线程1的步骤:
-
查询缓存,未找到:线程1首先检查缓存中是否有请求的数据。如果缓存中没有找到数据,线程1继续执行下一步。
-
获取互斥锁成功:线程1尝试获取一个互斥锁,以确保在更新缓存期间,其他线程不会同时执行数据库查询。如果获取成功,线程1继续执行。
-
查询数据库重建缓存数据:线程1在获取互斥锁后,查询数据库以获取所需的数据。
-
写入缓存:线程1将从数据库获取的数据写入缓存中,以便后续请求可以直接从缓存中获取数据。
-
释放锁:线程1完成数据的缓存写入后,释放互斥锁,允许其他线程获取锁并执行类似的操作。
线程2的步骤:
-
查询缓存,未找到:线程2同样首先检查缓存中是否有请求的数据。如果缓存中没有找到数据,线程2继续执行下一步。
-
获取互斥锁失败:线程2尝试获取互斥锁,但由于线程1已经获取了锁,线程2获取锁失败。
-
休眠一会,重试:线程2在获取互斥锁失败后,可以选择休眠一段时间,然后重试获取互斥锁。这是一种常见的避免活锁的策略。
-
线程1释放锁成功,缓存命中:当线程1释放锁后,线程2再次尝试获取互斥锁。如果此时线程1已经完成了数据库查询和缓存更新,并且释放了锁,线程2可以直接从缓存中获取数据。
通过这种方式,即使在高并发的情况下,也只有一个线程会去数据库查询数据,其他线程会等待互斥锁释放后,直接从缓存中获取数据,从而避免了缓存击穿的问题。
逻辑过期(提前预热)
逻辑过期具有高可用,性能优的特点,但不能保持数据的绝对一致
逻辑过期解决缓存击穿的方法是在缓存数据中加入一个逻辑上的过期时间,当缓存被访问时,检查这个逻辑上的过期时间,如果数据未过期,则直接返回缓存数据;如果数据已过期,则进行数据更新,并在更新期间仍然返回旧的数据,以保证服务的连续性。以下是具体的步骤和流程图:
流程图:
根据以上图示,逻辑过期解决缓存击穿的步骤可以详细描述如下:
线程1的步骤:
-
查询缓存,发现逻辑时间过期:线程1首先检查缓存中是否存在该数据,并检查数据是否逻辑过期。
-
获取互斥锁成功:线程1尝试获取一个互斥锁,以确保在更新缓存期间,其他线程不会同时执行数据库查询。如果获取成功,线程1继续执行。
-
开启一个独立线程:线程1开启一个独立线程(线程2)来处理缓存数据的更新。
-
返回过期数据:在独立线程更新缓存数据期间,线程1继续处理请求,返回过期的数据给用户,以减少用户等待时间。
线程2的步骤:
-
查询数据库更新缓存数据:线程2查询数据库以获取最新的数据。
-
写入缓存,重置逻辑过期时间:线程2将从数据库获取的最新数据写入缓存中,并重置逻辑过期时间。
-
释放锁:线程2完成数据的缓存写入后,释放互斥锁,允许其他线程获取锁并执行类似的操作。
通过这种方式,即使缓存数据逻辑过期,用户仍然能够快速地获取到数据,而不会因为等待数据更新而受到影响。同时,这也避免了大量请求同时击穿缓存,直接打到数据库,从而保护了数据库不受大量并发请求的影响。
这种模式通常被称为==“惰性加载”或“后台更新”==,它允许系统在不影响用户体验的前提下,异步地更新缓存数据。