1、RedisData 实体类
@Data
public class RedisData {
private LocalDateTime expireTime; // 逻辑过期时间
private Object data; // 真实数据
}
2、逻辑过期查询
// 线程池:用来异步重建缓存
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
// 锁前缀
public static final String LOCK_SHOP_KEY = "lock:shop:";
@Override
public Shop queryWithLogicalExpire(Long id) {
String key = CACHE_SHOP_KEY + id;
// 1. 从Redis查缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2. 缓存不存在,直接返回null
if (StrUtil.isBlank(shopJson)) {
return null;
}
// 3. 缓存存在,反序列化
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
// 4. 判断是否过期
if (expireTime.isAfter(LocalDateTime.now())) {
// 未过期,直接返回
return shop;
}
// 5. 已过期,尝试获取锁
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
// 6. 拿到锁,开独立线程重建缓存
if (isLock) {
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
this.saveShop2Redis(id, 20L); // 缓存重建
} catch (Exception e) {
e.printStackTrace();
} finally {
unlock(lockKey); // 释放锁
}
});
}
// 7. 无论有没拿到锁,都返回旧数据
return shop;
}
3、缓存重建方法
private void saveShop2Redis(Long id, Long expireSeconds) {
// 1. 查询数据库
Shop shop = getById(id);
// 2. 封装逻辑过期对象
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
// 3. 写入Redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}
4、分布式锁
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);
}