基于互斥锁 解决 缓存击穿的解决方案
什么是缓存击穿
缓存击穿问题也叫热点Key问题,
就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,
无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案
- 互斥锁(时间换空间)
- 优点:内存占用小,一致性高,实现简单
- 缺点:性能较低,容易出现死锁
- 逻辑过期(空间换时间)
- 优点:性能高
- 缺点:内存占用较大,容易出现脏读
两者相比较,互斥锁更加易于实现,但是容易发生死锁,且锁导致并行变成串行,导致系统性能下降。
逻辑过期实现起来相较复杂,且需要耗费额外的内存,但是通过开启子线程重建缓存,使原来的同步阻塞变成异步,提高系统的响应速度,但是容易出现脏读
逻辑图

请求 -> Redis 有数据? -> 有:返回
↓
没有数据 -> 是空字符串? -> 是:返回null
↓
否:尝试加锁 -> 加锁失败:休眠重试
↓
加锁成功 -> 查数据库
↓
数据为空 -> 缓存空字符串
有数据 -> 缓存结果
↓
返回数据
实现
/*
二、缓存击穿的解决:法一,互斥锁
引入“互斥锁”来确保只有一个线程去查询数据库和重建缓存,其他线程等待或重试。
*/
public Shop querywithjichuan_mutex(Long id) {
// 1. 先从Redis中查,这里的常量值是固定的前缀 + 店铺id
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
/*
2. 判断缓存是否命中
2.1 缓存命中了
2.1.1 如果 shopJson 不为空(字符串不是 null、""、全空格),说明缓存中有数据。
把 json 字符串转为 Shop 对象直接返回,不需要访问数据库。
*/
if (StrUtil.isNotBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return shop;
}
/* 2.1.2 如果查询到的是空字符串“”,则说明是我们缓存的空数据
之前数据库查不到这条数据,我们会把一个空字符串放进缓存。
这样后续请求查到这个空字符串就不会再去访问数据库,避免缓存穿透。
*/
if (shopJson != null) {
return null;
}
/*
2.2 缓存未命中,开始加锁重建缓存
实现在高并发的情况下缓存重建
调用 tryLock() 尝试获取互斥锁(通常是 Redis 的 SETNX 实现)。
*/
Shop shop = null;
try {
// 获取互斥锁
boolean flag = tryLock(LOCK_SHOP_KEY + id);
/* 2.2.1 加锁失败就休眠重试(自旋)
如果拿不到锁,说明其他线程正在重建缓存。
当前线程休眠 50 毫秒后递归调用自己重新尝试查询缓存。
这个过程称为“自旋锁”或“延迟重试”。
*/
while (!flag) {
Thread.sleep(50);
return querywithjichuan_mutex(id);
}
// 2.2.2 获取成功->读取数据库,重建缓存
// 2.2.2.1 查不到,则将空值写入Redis
shop = getById(id);
if (shop == null) {
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
/*
2.2.2.2 数据库中有数据,写入缓存
查到了则转为json字符串
*/
String jsonStr = JSONUtil.toJsonStr(shop);
// 并存入redis,设置TTL
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr, CACHE_SHOP_TTL, TimeUnit.MINUTES);
// 最终把查询到的商户信息返回给前端
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 2.2.3 最后:释放锁
unlock(LOCK_SHOP_KEY + id);
}
return shop;
}
- 使用Redis中的setnx指令实现互斥锁,只有当值不存在时才能进行set操作
- 锁的有效期更具体业务有关,需要灵活变动,一般锁的有效期是业务处理时长10~20倍
- 线程获取锁后,还需要查询缓存(也就是所谓的双检),这样才能够真正有效保障缓存不被击穿

浙公网安备 33010602011771号