文章目录
一、缓存穿透
1、什么是缓存穿透
缓存穿透是指客户端请求的数据在缓存数据与数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
2、如何解决穿透问题
2、1这里采用的是缓存空对象的方式:
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY + id;
// 从Redis 查询 商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 判断是否存在
if(StrUtil.isNotBlank(shopJson)){
// 存在返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 判断在redis命中的是否是空值
if(shopJson != null){
// 返回错误信息
return Result.fail("店铺信息不存在");
}
// 不存在,根据id查询数据库
Shop shop = getById(id);
if (shop == null){
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
// 不存在,返回错误
return Result.fail("店铺不存在");
}
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 存在,写入reids 返回
return Result.ok(shop);
}
二、缓存雪崩
1、 什么是缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来的巨大压力。
2、解决方案
2、1 给缓存业务添加降级限流策略
在发生此类问题时,将访问进行拒绝,限制访问等方式,降低服务器的压力,牺牲部分服务保证数据库,服务器的健康性。
2、2 给业务添加多级缓存
给架构中各个部分添加缓存,例如Nginx,Tomcat 等各个环节添加缓存来应对这些问题的发生。
三、缓存击穿
1、什么是缓存击穿问题:
缓存击穿问题也叫热点key问题,是指一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
2、解决方式:
2.1 互斥锁与逻辑过期 解决方案的优缺点
2.2 基于互斥锁解决击穿问题
1、业务流程图:
2、实现代码
public Shop queryWithMutex(Long id) {
String key = CACHE_SHOP_KEY + id;
// 从Redis 查询 商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 判断是否存在
if(StrUtil.isNotBlank(shopJson)){
// 存在返回
return JSONUtil.toBean(shopJson, Shop.class);
}
// 判断在redis命中的是否是空值
if(shopJson != null){
// 返回错误信息
return null;
}
// 开始实现缓存重建
// 获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
// 判断是否成功
if(!isLock){
// 失败 进行休眠
Thread.sleep(50);
return queryWithMutex(id);
}
// 成功 根据id 进行查询数据库
// 不存在,根据id查询数据库
shop = getById(id);
if (shop == null){
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
// 不存在,返回错误
return null;
}
// 存在,写入reids 返回
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
// 释放互斥锁
unlock(lockKey);
}
// 返回
return shop;
}
2.2 基于逻辑过期方式解决击穿问题
1、业务流程:
2、实现代码:
public Result queryById(Long id) {
// 缓存穿透
// 使用逻辑过期来解决缓存击穿问题
Shop shop = queryWithLogincalExpire(id);
if (shop == null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}
public Shop queryWithLogincalExpire(Long id) {
String key = CACHE_SHOP_KEY + id;
// 从Redis 查询 商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 判断是否存在
if(StrUtil.isBlank(shopJson)){
// 不存在返回
return null;
}
// 命中需要先把Json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
// 判断数据是否过期
if (expireTime.isAfter(LocalDateTime.now())){
// 未过期直接返回店铺信息
return shop;
}
// 已过期 需要缓存重建
// 获取互斥锁
String lockKey =LOCK_SHOP_KEY+id;
boolean isLock = tryLock(lockKey);
// 判断是否获取锁成功
if (isLock){
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
this.saveShop2Redis(id,30L);
} catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
unlock(lockKey);
}
});
}
return shop;
}