[转] Redis 缓存穿透、缓存击穿、缓存雪崩
参考链接: https://juejin.cn/post/68449041323235901512个月前 • 143次点击 • 来自 后端
原文地址:图解缓存穿透、缓存击穿、缓存雪崩的区别,附解决方法
最早接触3个概念是看尚硅谷的教学视频,里面也提供相应的代码解决方案,缓存3种问题都发生在没有命中缓存的情况下 - 缓存穿透、缓存击穿、缓存雪崩,以下的解决过程已经处理完以上三种情况:
public PmsSkuInfo getSkuById(String skuId,String ip) {
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"进入的商品详情的请求");
PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
// 链接缓存,查询缓存
Jedis jedis = redisUtil.getJedis();
String skuKey = "sku:"+skuId+":info";
String skuJson = jedis.get(skuKey);
if(StringUtils.isNotBlank(skuJson)){
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"从缓存中获取商品详情");
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
}else{
// 如果缓存中没有,查询mysql
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"发现缓存中没有,申请缓存的分布式锁:"+"sku:" + skuId + ":lock");
// 设置分布式锁
String token = UUID.randomUUID().toString();
String OK = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10*1000);// 拿到锁的线程有10秒的过期时间
if(StringUtils.isNotBlank(OK)&&OK.equals("OK")){
// 设置成功,有权在10秒的过期时间内访问数据库
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"有权在10秒的过期时间内访问数据库:"+"sku:" + skuId + ":lock");
pmsSkuInfo = getSkuByIdFromDb(skuId);
if(pmsSkuInfo!=null){
// mysql查询结果存入redis
jedis.set("sku:"+skuId+":info",JSON.toJSONString(pmsSkuInfo));
}else{
// 数据库中不存在该sku
// 为了防止缓存穿透将,null或者空字符串值设置给redis
jedis.setex("sku:"+skuId+":info",60*3,JSON.toJSONString(""));
}
// 在访问mysql后,将mysql的分布锁释放
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"使用完毕,将锁归还:"+"sku:" + skuId + ":lock");
String lockToken = jedis.get("sku:" + skuId + ":lock");
// 处理锁过期 使用UUID生成随机识别用户锁
if(StringUtils.isNotBlank(lockToken)&&lockToken.equals(token)){
//jedis.eval("lua");可与用lua脚本,在查询到key的同时删除该key,防止高并发下的意外的发生
jedis.del("sku:" + skuId + ":lock");// 用token确认删除的是自己的sku的锁
}
}else{
// 设置失败,自旋(该线程在睡眠几秒后,重新尝试访问本方法)
System.out.println("ip为"+ip+"的同学:"+Thread.currentThread().getName()+"没有拿到锁,开始自旋");
return getSkuById(skuId,ip);
}
}
jedis.close();
return pmsSkuInfo;
}
虽然3个概念都懂,也能简单的处理相关的问题,但是上次面试问到相关问题依旧结结巴巴的,使用此文来复习下八股面试。
1.缓存处理流程
1.1 前台get请求,后台服务从缓存中获取数据
1.2 前台post/put请求来创建或修改数据,后台缓存处理流程
2.缓存穿透、缓存击穿、缓存雪崩
2.1 缓存穿透
- 缓存穿透是指缓存和数据库中都没有的数据。用户不断发起请求,caceh拦截不了,直接使用数据库查询数据,可能会对数据库性能造成影响。
- 图解
- 解决方法(1.设置key-value,value的值为null; 2.将用户请求拦截在上游)
2.2 缓存击穿
- 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期)。由于用户特别多,同时去读取数据,引起数据库压力瞬间增大。
- 图解
- 解决方法(1.设置热点数据不过期;2.加互斥锁,附参考代码) Redis回收机制不会动没有设置有效期的数据。所以未设置过期时间的cache会长期有效。
# 伪代码
dict_lock = {'lock':'开'} # 设置初始锁状态
url = ['^RebbitMQ$'] # 任务消息队列路由设置
RebbitMQ = ['server1','server2', . . .] # 服务线程添加到queue中
def open_lock(view):
"""获取数据操作权限,关锁"""
dict_lock.get('lock') = '关'
return Response(0)
def close_lock(view):
"""数据使用完毕,释放资源"""
dict_lock.get('lock') = '开'
return Response(1)
# 队列先进先出
RebbitMQ.last.open_lock() # 队列最后一个元素操作数据
RebbitMQ.last.delete() # 数据操作完毕,删除这个元素
RebbitMQ.close_lock() # 释放资源
2.3 缓存雪崩
- 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指的是并发地查询同一条数据,缓存雪崩是指不同数据都过期了,很多数据缓存拦截不了,从而查数据库。
- 图解(同缓存击穿)
- 解决方法
-
1.设置随机的过期时间,防止同一时间大量的数据过期现象
-
2.设置热点数据永不过期
-
3.使用分布式缓存,将热点数据均匀分布在不同的缓存数据库中。
- 分布式cache(多台服务器,每台服务器都启动了一个或多个redis服务)
-