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:";@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** redis缓存* 缓存击穿*/@Overridepublic BooksVo selectById(Long bookId) {// 缓存击穿-互斥锁// return tryCacheMutex(bookId);// 缓存击穿-逻辑过期时间return tryCacheMutex2(bookId);}/*** redis缓存* 缓存击穿-逻辑过期版本* @param bookId*/private BooksVo tryCacheMutex2(Long bookId) {// RedisKeyString 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 doubleString 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.写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + bookId, JSONUtil.toJsonStr(redisData));}
redisData实体类
@Data
public class RedisData {/** 逻辑过期时间 */private LocalDateTime expireSecond;/** 拓展实体类 */private Object data;
}