Redis-缓存击穿-逻辑过期实现
缓存击穿:也称热点key问题,大量访问一个key,而这个key恰巧到期了,导致大量的请求访问数据库。增大数据库的负担。为了解决这个问题可以采用互斥锁或逻辑过期的方式解决。本章采用逻辑过期的方式解决此问题。
流程图:
第一点需要进行缓存预热,把经常用的key预先缓存到redis中
/** 缓存数据KEY */
private final String CACHE_SHOP_KEY = "CACHE_SHOP_KEY:";
/** 缓存互斥锁KEY */
private final String CACHE_SHOP_LOCK_KEY = "CACHE_SHOP_LOCK_KEY:";
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
/**
* redis缓存
* 缓存击穿
*/
@Override
public BooksVo selectById(Long bookId) {
// 缓存击穿-互斥锁
// return tryCacheMutex(bookId);
// 缓存击穿-逻辑过期时间
return tryCacheMutex2(bookId);
}
/**
* redis缓存
* 缓存击穿-逻辑过期版本
* @param bookId
*/
private BooksVo tryCacheMutex2(Long bookId) {
// RedisKey
String cacheKey = CACHE_SHOP_KEY + bookId;
// 1.从Redis查询商铺缓存
// 获取缓存数据
String contentBook = stringRedisTemplate.opsForValue().get(cacheKey);
// 2.判断缓存是否命中
if (StringUtils.isBlank(contentBook)){
// 3.1缓存未命中 直接返回结果
return null;
}
// 3.2缓存命中-获取数据
RedisData redisData = JSONUtil.toBean(contentBook, RedisData.class);
BooksVo booksVo = JSONUtil.toBean((JSONObject) redisData.getData(), BooksVo.class);
LocalDateTime expireSecond = redisData.getExpireSecond();
// 4.缓存未过期 直接返回数据
if (expireSecond.isAfter(LocalDateTime.now())){
return booksVo;
}
// 5.缓存过期-获取互斥锁
if (tryLock(bookId)){
// check double
String s = stringRedisTemplate.opsForValue().get(cacheKey);
RedisData redisData1 = JSONUtil.toBean(s, RedisData.class);
if (BooleanUtil.isFalse(redisData1.getExpireSecond().isAfter(LocalDateTime.now()))){
// 获取互斥锁成功,开启独立线程,缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
// 重建缓存
saveCacheBook(bookId, 20L);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 释放互斥锁
stringRedisTemplate.delete(CACHE_SHOP_LOCK_KEY + bookId);
}
});
}
}
// 返回已过期的数据
return booksVo;
}
/**
* 保存缓存信息
*/
public void saveCacheBook(Long bookId, Long expireSeconds){
// 1.查询数据库数据
BooksVo booksVo = this.queryById(bookId);
// 2.封装逻辑过期时间
RedisData redisData = new RedisData();
redisData.setData(booksVo);
// 获取当前的时间 + 指定秒数
redisData.setExpireSecond(LocalDateTime.now().plusSeconds(expireSeconds));
// 3.写入redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + bookId, JSONUtil.toJsonStr(redisData));
}
redisData实体类
@Data
public class RedisData {
/** 逻辑过期时间 */
private LocalDateTime expireSecond;
/** 拓展实体类 */
private Object data;
}