🎉🎉欢迎光临,终于等到你啦🎉🎉
🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀
🌟持续更新的专栏Redis实战与进阶
本专栏讲解Redis从原理到实践
这是苏泽的个人主页可以看到我其他的内容哦👇👇
努力的苏泽http://suzee.blog.csdn.net
本期讲解Redis企业必考面试题 缓存击穿使用逻辑过期解决
首先要了解什么是缓存击穿可以看我这一篇:http://t.csdnimg.cn/jMAqw
那么我们复原一下业务场景
分析思路
-
确定数据的更新逻辑:首先,需要明确数据的更新逻辑。了解数据是如何被修改、更新或者删除的,以及这些操作是由哪些业务逻辑触发的。这可以包括数据库更新、后端服务的数据变更通知等。
-
监听数据的更新事件:在数据被修改、更新或者删除时,需要能够捕捉到这些事件。这可以通过数据库的触发器(Trigger)机制、消息队列、发布-订阅模式等方式来实现。目的是在数据更新时能够及时感知到。
-
更新缓存和设置逻辑过期时间:当接收到数据更新事件时,需要更新相应的缓存,并重新设置逻辑过期时间。这意味着需要将最新的数据加载到缓存中,并根据业务规则设置适当的过期时间。这可以通过缓存服务的API或者命令来完成。
-
缓存访问时的逻辑判断:在每次访问缓存之前,需要进行逻辑判断以确定数据是否过期。这可以基于缓存中存储的逻辑过期时间和当前时间进行比较。如果数据已经过期,需要重新加载最新数据到缓存中。
-
数据加载的并发控制:在数据过期时,可能会有多个线程同时检测到数据过期并尝试重新加载数据到缓存。为了避免并发查询对后端服务造成压力,可以使用互斥锁或其他并发控制机制,确保只有一个线程负责重新加载数据,其他线程从缓存中获取旧数据。
-
定期刷新数据:除了根据数据更新事件进行缓存更新外,还可以定期刷新数据来保持缓存的新鲜性。定期刷新可以通过设置一个时间间隔,在规定的时间间隔内重新加载数据到缓存中,避免数据长时间未更新而导致的过期数据。
代码实现
首先
我们先写一个RedisData的类专门封装到期日期
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}
然后在ShopServiceImpl中写一个专门处理逻辑过期的方法saveShop2Redis
//数据预热
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));
}
然后再写一个专门处理逻辑过期的方法queryWithLogicalExpiration
//搭建一个线程池
private static final ExecutorService executor = Executors.newFixedThreadPool(10);
//解决缓存穿透问题
public Shop queryWithLogicalExpiration(long id){
String key = CACHE_SHOP_KEY + id;
//1.先查询Redis
String Jsonshop = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if (StrUtil.isBlank(Jsonshop)){
//不存在 直接返回空
return null;
}
//3.命中 先反序列化为对象
RedisData redisData = JSONUtil.toBean(Jsonshop, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
//4.查询是否过期
if (redisData.getExpireTime().isAfter(LocalDateTime.now())){
//5.未过期 直接返回
return shop;
}
//6.过期 重构缓存
String lockKey = LOCK_SHOP_KEY + id;
//6.1获得互斥锁
// 6.2判断是否成功
if(tryLock(lockKey)){
//6.3成功 开启新线程 实现重构缓存
executor.submit(() ->{
//重建缓存
try {
//这里做的就是1.先查数据库2.然后写入Redis
//其实就是重构缓存
this.saveShop2Redis(id,30L);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
//关锁
unLock(lockKey);
}
});
}
//6.4失败 直接返回旧数据
return shop;
}