逻辑过期是一种缓存策略,通过在缓存中存储数据的同时,记录一个过期时间。在缓存未过期时,直接返回缓存数据;在缓存过期后,即使缓存中的数据过期,也返回旧数据,同时异步刷新缓存。这种机制保证了缓存的高可用性,并减少了对数据库的直接访问。
public Shop queryWithLogicalExpire(Long id) {
// 1. 从redis中查询商铺缓存
String json = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
// 2. 如果未命中,则返回空
if (StrUtil.isBlank(json)) {
return null;
}
// 3. 命中,将json反序列化为对象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
// 3.1 将data转为Shop对象
JSONObject shopJson = (JSONObject) redisData.getData();
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
// 3.2 获取过期时间
LocalDateTime expireTime = redisData.getExpireTime();
// 4. 判断是否过期
if (LocalDateTime.now().isBefore(expireTime)) {
// 5. 未过期,直接返回商铺信息
return shop;
}
// 6. 过期,尝试获取互斥锁
boolean flag = tryLock(LOCK_SHOP_KEY + id);
// 7. 获取到了锁
if (flag) {
// 8. 开启独立线程
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
this.saveShop2Redis(id, LOCK_SHOP_TTL);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
unlock(LOCK_SHOP_KEY + id);
}
});
// 9. 直接返回商铺信息
return shop;
}
// 10. 未获取到锁,直接返回商铺信息
return shop;
}
public void saveShop2Redis(Long id, Long expireSeconds) {
Shop shop = getById(id);
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}
优点
- 高可用性:即使缓存过期,也可以快速返回旧数据,提升系统的响应速度。
- 减少数据库压力:通过异步刷新缓存,减少对数据库的直接访问,避免数据库压力骤增。