Redis缓存击穿是指在高并发的情况下,当某个热点数据的缓存过期或不存在时,大量的请求同时涌入数据库或后端服务,导致数据库或后端服务负载过高,甚至崩溃的情况。
当涉及到并发访问共享资源时,互斥锁和逻辑过期是两种常用的技术手段。
先写两个函数 一个加锁 一个释放锁
private boolean tryLock(String key){
//自定义互斥锁 将申请锁的结果返回
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
//释放锁
private void unLock(String key){
stringRedisTemplate.delete(key);
}
public Result queryById(Long id) {
//1.从Redis查询id 这里使用的数据结构可以是String也可以是hash 若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否存在 在字符串意义上是否为空
if (StrUtil.isNotBlank(shopJson)) {
//3.存在直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//3.判断是否命中写入redis中的null
if(shopJson != null){
return Result.fail("店铺信息不能为空!");
}
//4.不存在 查询数据库
Shop shop = getById(id);
//5.数据库中不存在 返回报错
if (shop == null){
//空值写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
return Result.fail("店铺不存在!");
}
//6.数据库中存在 写入Redis 并返回
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
return Result.ok(shop);
}
//封装缓存穿透函数
private Shop queryWithPassThrough(Long id){
//1.从Redis查询id 这里使用的数据结构可以是String也可以是hash 若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否 在字符串意义上是否为空
if (StrUtil.isNotBlank(shopJson)) {
//3.存在直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
//3.判断是否命中写入redis中的null
if(shopJson != null){
return null;
}
Shop shop = getById(id);
//5.数据库中不存在 返回报错
if (shop == null){
//空值null写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
//6.数据库中存在 写入Redis 并返回
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
return shop;
}
public Result queryById(Long id) {
//缓存穿透
// Shop shop = queryWithPassThrough(id);
//互斥锁解决缓存穿透
Shop shop = queryWithMutex(id);
if (shop == null) {
return Result.fail("店铺不存在");
}
//返回
return Result.ok(shop);
}
//解决缓存穿透的问题
private Shop queryWithMutex(Long id) throws InterruptedException{
String lockKey = "lockKey" + id;
while (true) {
//1.从Redis查询id 这里使用的数据结构可以是String也可以是hash 若是查询不到就为空了 CACHE_SHOP_KEY就是"cache:shop:"
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
//2.判断是否 在字符串意义上是否为空
if (StrUtil.isNotBlank(shopJson)) {
//3.存在直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
//3.判断是否命中写入redis中的null
if(shopJson != null){
return null;
}
//4重建缓存
//4.1申请互斥锁
boolean flag = tryLock(lockKey);
//4.2判断是否成功
if (!flag){
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
else//成功了就跳出循环 这里不敢用递归 怕栈溢出了
break;
}
//4.4 成功 查询数据库
Shop shop = getById(id);
//5.数据库中不存在 返回报错
if (shop == null){
//空值null写入redis
stringRedisTemplate.opsForValue().set("cache:shop:" + id, null,CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
//6.数据库中存在 写入Redis 并返回
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
//7.释放互斥锁
unLock(lockKey);
return shop;
}
互斥锁作为一种并发控制机制,在解决缓存击穿问题时具有以下优点和缺点: