Redis 缓存击穿问题 解决方案(二) 逻辑过期
逻辑过期方案:用户查询某个热门产品信息,如果缓存未命中(即信息为空),则直接返回空,不去查询数据库。如果缓存信息命中,则判断是否逻辑过期,未过期返回缓存信息,过期则重建缓存,尝试获得互斥锁,获取失败则直接返回已过期缓存数据,获取成功则开启独立线程去重构缓存然后直接返回旧的缓存信息,重构完成之后就释放互斥锁。由于线程获得锁之后要开启独立线程去重构缓存信息,那么我们最好提前声明定义一个线程池。这
Redis 缓存击穿问题 解决方案(二) 逻辑过期
逻辑过期方案:用户查询某个热门产品信息,如果缓存未命中(即信息为空),则直接返回空,不去查询数据库。如果缓存信息命中,则判断是否逻辑过期,未过期返回缓存信息,过期则重建缓存,尝试获得互斥锁,获取失败则直接返回已过期缓存数据,获取成功则开启独立线程去重构缓存然后直接返回旧的缓存信息,重构完成之后就释放互斥锁。
这样看来,只要命中了缓存,无论是否过期,是否获得锁看,都返回同一个缓存信息。
逻辑过期
的流程图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lpmbyhV-1664181525415)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20220910112759246.png)]
查询某个热门产品信息的方法,如下。
@Override
public Result queryById(Long id) {
//缓存穿透
//Shop shop = queryWithPassThrough(id);
//互斥锁解决缓存击穿
//Shop shop = queryWithMutex(id);
//逻辑过期解决缓存击穿
Shop shop = queryWithLogicalExpire(id);
if (shop == null) {
return Result.fail("店铺不存在!");
}
//返回
return Result.ok(shop);
}
这里解决 缓存击穿
问题的主要业务方法写在了 queryWithLogicalExpire(id)
中,完整代码如下:
//逻辑过期
public Shop queryWithLogicalExpire(Long id) {
String key = CACHE_SHOP_KEY + id;
//1.从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if (StrUtil.isBlank(shopJson)) {
//3.未命中
return null;
}
//4.命中,需要先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = (Shop) redisData.getData();
LocalDateTime expireTime = redisData.getExpireTime();
//5.判断是否过期
if (expireTime.isAfter(LocalDateTime.now())) {
//5.1还未过期
return shop;
}
//5.2已经过期,需要缓存重建
//6.缓存重建
//6.1获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
//6.2判断是否获取锁成功
if (isLock) {
// 6.3成功,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
//重建缓存
this.saveShop2Redis(id, 20L);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
unlock(lockKey);
}
});
}
//6.4返回过期的店铺信息
//7.返回
return shop;
}
由于线程获得锁之后要开启独立线程去重构缓存信息,那么我们最好提前声明定义一个线程池。
//线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
除了线程池外,我们还需要 获得锁
和 释放锁
的方法,具体代码如下:
//获得锁
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
//释放锁
private void unlock(String key) {
stringRedisTemplate.delete(key);
}
至此,利用 逻辑过期
解决缓存击穿问题就结束了。
更多推荐
所有评论(0)