场景
也是热点key问题,就是一个高并发访问并且缓存重建较为复杂的key突然失效了,这里的key失效可以理解为某电商平台在节日大促,同时段大量请求访问一个商品,这个商品key会存在一个固定TTL,若TTL到时了,key消失,仍有大量请求访问该商品,这个key的重建业务复杂,耗时又高。于是,请求都来到数据库拿数据,瞬间给数据库造成了巨大的压力。
解决方案
互斥锁
在发起请求未命中redis缓存时,表示此信息不存在,或过期,尝试获取锁。 若没拿到锁,表示此数据正在被更新,线程进行休眠再递归重新从缓存获取数据。 若拿到了锁,根据id查数据库,将数据信息写入redis并释放互斥锁,返回数据。
ps:图片来自B站黑马程序员
redis中有命令
setnx key value
表示key不存在就set,存在就无法赋值,这就可以应用到锁中
对应方法setIfAbsent(),再设置过期时间,避免死锁
代码语言:javascript复制private boolean tryLock(String key) {//获取锁
Boolean flag = stringRedisTemplate
.opsForValue()
.setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
//锁的存在时间,几秒就行
return BooleanUtil.isTrue(flag);//flag可能为null,进行拆箱封箱
}
模拟:封装在了queryWithMutex()中,开始店铺信息不存在redis中,当请求需要获取店铺信息时,redis没命中,那么获取锁, 进行缓存写入操作,成功后释放锁并返回数据。然后其他请求就能从redis中拿到数据了。 ps:其中有部分缓存穿透操作,请前往redis缓存穿透
代码语言:javascript复制 @Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {
//缓存穿透
// Shop shop = queryWithPassThrough(id);
//互斥锁解决缓存击穿
Shop shop = queryWithMutex(id);
if (shop == null) {
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}
public Shop queryWithMutex(Long id) {
//从redis查询商铺缓存
String key = CACHE_SHOP_KEY id;//缓存的key
String shopJson = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(shopJson)) {
//存在直接返回
return JSONUtil.toBean(shopJson, Shop.class);
}
//判断命中的是否为空值
if (shopJson != null) {//shopJson==""
//返回一个错误信息
return null;
}
//4.redis中不存在,实现缓存重建
//4.1获取互斥锁
String lockKey = LOCK_SHOP_KEY id;
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
//4.2判断是否获取成功
if (!isLock) {//获取