柒夭八股:缓存击穿的解决方案



这个主要是最近准备八股文缓存专题时做的笔记,主要是对于缓存击穿的解决方案,目前总结起来大概三种

分别是缓存预热、分布式锁以及永不过期,这三种这里做了详细的讲述,看看到过年前能不能把缓存专题给整完,fighting!




什么是缓存击穿?




缓存击穿是指在高并发系统中,一个热点数据缓存的过期或者缓存中的热点数据不存在,导致大量并发请求直接访问数据库,从而给数据库造成巨大压力,甚至宕机的现象。

说具体点就是当某个热点数据在缓存中过期了,如果此时有大量并发请求同时访问这个数据,由于缓存中这个数据不存在,其所有的请求都会直接访问数据库,从而导致数据库压力急剧增加。




如何解决缓存击穿?




一般来说,解决缓存击穿的方法主要有三种:

  1. 加分布式锁
  2. 热点数据预加载(缓存预热)
  3. 热点数据永不过期




1. 分布式锁




这里我们来逐步进行分析,分析如何通过互斥锁的方式来解决缓存击穿问题、





1、查询缓存不存在查询数据库





这个就是第一版的解决方案,也是我们对于普通数据的缓存方案,这个方案首先查询缓存中对应的数据是否存在,如果不存在就请求数据库,数据库中存在就将当前数据写入到缓存中,这个方案的问题也比较明显:

如果缓存中热点数据的缓存过期或者被删除,大量的请求将会打到数据库,从而导致数据库压力较大。

伪代码如下:

public String queryInfo(String id){
// 1. 从缓存中获取对应的数据
String cacheData = cache.get(id);
// 2.判断获取的数据是否为空
if(cacheData == null){
// 查询缓存数据为空,,查询数据库
String dbData = db.queryById(id);
if(dbData != null){
cache.set(id,data);
cacheData = dbData;
}
}
// 返回数据
return cacheData;
}





2、通过分布式互斥锁的方案降低数据库压力





分布式锁的解决方案就是保证只有一个请求可以访问数据库,其他需要访问数据库的请求等待结果。这样的话可以避免大量的请求同时访问数据库。

这个主要是在原有的基础上进行一定的调整,即限制对于数据库的请求,每次只有一个请求可以访问数据库,这种方案可以有效地避免缓存击穿问题,因为只有一个线程可以在同一时间内查询数据库,其他线程则需要等待,这样的话就不会同时穿透到数据库。

其主要在原有的基础上进行修改,伪代码如下:

public String queryInfo(String id){
// 1. 从缓存中获取对应的数据
String cacheData = cache.get(id);
// 2.判断获取的数据是否为空
if(cacheData == null){
// 查询缓存数据为空,,查询数据库
Lock lock = getLock(id);
lock.lock();
try{
String dbData = db.queryById(id);
if(dbData != null){
cache.set(id,data);
cacheData = dbData;
}
}finally{
lock.unlock();
}
}
// 返回数据
return cacheData;
}

但是这样有一个弊端,那就是获取分布式锁的请求,都会执行一次查询数据库的请求,然后将查询结果更新到缓存中去。但是从理论上来说,只有第一次加载数据库记录的请求是有效的。

这种情况会导致以下两个问题的发生:

  1. 海量的用户获取锁之后都查询数据库,这样的话会造成数据库性能的浪费,因为如此多的请求当中,只有第一次的查询请求是有效的,其他的查询请求都是可以避免的。
  2. 查询数据库过多的话可能会导致用户响应时间变长,接口吞吐量下降等情况

所以,针对以上情况,可以采用双重判定锁的方式来进行优化。

双重判定锁的逻辑很简单,如下图所示,在查询数据库前,再次查询一下缓存,查看缓存中是否存在对应的数据,如果存在,则直接返回,不存在才去查询数据库。


根据以上情况,其伪代码如下:

public String queryInfo(String id){
// 1. 从缓存中获取对应的数据
String cacheData = cache.get(id);
// 2.判断获取的数据是否为空
if(cacheData == null){
// 查询缓存数据为空,,查询数据库
Lock lock = getLock(id);
lock.lock();
try{
// 进行缓存数据的二次判断
cacheData = cache.get(id);
if(cacheData == null){
// 缓存数据为空
String dbData = db.queryById(id);
if(dbData != null){
cache.set(id,data);
cacheData = dbData;
}
}
}finally{
lock.unlock();
}
}
// 返回数据
return cacheData;
}

总结一下双重判断锁的逻辑

  1. 获取锁:在查询数据库之前,先尝试获取一个分布式锁,只有一个线程可以成功获取锁,其他线程等待。
  2. 查询数据库:如果双重判断锁确认缓存中不存在对应数据,那么执行查询数据库操作,获取数据。
  3. 将数据写入缓存:获取到数据之后,将数据写入缓存,并且设置一个合适的过期时间,以防止缓存永远不会被更新
  4. 释放锁:最后,释放获取的锁。使得其他线程可以继续使用这个锁。




2. 热点数据预加载




热点数据预加载,在活动开始之前,针对已知的热点数据从数据库加载到缓存中,这样的话可以避免海量的请求第一次访问热点数据的时候需要从数据库读取的流程,降低数据库的压力。

可以极大地减少请求的响应时间,有效避免缓存击穿。



3. 热点数据永不过期



热点数据永不过期,指的是将可以预知的热点数据,在活动开始之前,将过期时间设置为 -1,这样的话就不会有缓存击穿的风险了。

这个可以搭配热点数据预加载的方案一起实现,等到热点数据的时间结束之后,其数据的访问量降低了,通天阁后台任务的方案针对缓存设置过期时间,从而降低 Redis 存储的压力。



0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
答案说明所有
作者分享
🌟择难路,未有疑,四非学院本运气拉满,春招拿下大厂后端
160
快了,xdm,由于这周上班(小加班),写得有点慢,已经抓紧码字了😝
7
在学校的时候忙毕业的事(主要享受学校时光,有点懒),就每天写一点,发现还不如推倒重写,这今天整理一下😁,先预告一手
11
分析一个真人真事,就是我舍友下午去了一个测试的公司,然后那边说实训2个月,后面打一年工抵工资,我舍友来问我需要注意什么,就我看了一下合同,感觉有点像是招转陪,后面上网查了一下确实是,所以在这里警醒大家,找工作千万要注意,不要因为急给骗了😂
13
晓多科技社招一面 周天体测完全身酸痛,就没去上班,请假在家面试,然后上周投了一个社招1~3 年的岗位,就简单面了一下: 1. 自我介绍 2. 询问背景情况,确认社招,然后校招通过可以提前实习 3. 询问了一下实习情况 4. 直接做一道题,就直接给一个白板,原本要用 IDEA 写,但是我 IDEA 启动不了,直接白板写了,题目要求代码实现,包括数据库表设计和设计模式,我数据库表设计直接用 class 实体类代替了,问题不是很大,然后回答单例完成 ID 生成器包装,策略完成不同类型优惠券计算,然后工厂针对优惠券返回,然后加上责任链进行优惠卷相关参数校验,然后我这里 ID 生成说的时候用雪花算法+基因法冗余了用户 ID 号,然后从 ThreadLocal 里面取出用户 ID对比,避免水平越权,然后代码实现工厂、策略,差不多就这些了 /** * 题目1:电商优惠券系统设计 业务场景: 设计一个电商平台的优惠券系统,需满足: 1.支持多种优惠类型(满减、折扣、赠品)。 2.优惠券可叠加使用,但需校验适用范围(如特定商品/用户等级)。 3.需记录优惠券领取、使用记录,并支持运营按“用户领取量”和“券使用率”分析数据。 重点: 1.数据建模(数据表与关系设计)。 2.面向对象设计(要素+设计模式)。 * * */ 5. 算法题:双向链表反转,直接白板手敲 出去差不多半小时 HR 反馈过了,二面
17
下载 APP