图灵重生我名苏泽

图灵重生我名苏泽

SpringBoot整合Redis:面试必考题-缓存击穿--逻辑过期解决-LMLPHP

🎉🎉欢迎光临,终于等到你啦🎉🎉

🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀

🌟持续更新的专栏Redis实战与进阶

本专栏讲解Redis从原理到实践

这是苏泽的个人主页可以看到我其他的内容哦👇👇

努力的苏泽SpringBoot整合Redis:面试必考题-缓存击穿--逻辑过期解决-LMLPHPhttp://suzee.blog.csdn.net


本期讲解Redis企业必考面试题  缓存击穿使用逻辑过期解决

首先要了解什么是缓存击穿可以看我这一篇:http://t.csdnimg.cn/jMAqw

那么我们复原一下业务场景

SpringBoot整合Redis:面试必考题-缓存击穿--逻辑过期解决-LMLPHP

分析思路

  1. 确定数据的更新逻辑:首先,需要明确数据的更新逻辑。了解数据是如何被修改、更新或者删除的,以及这些操作是由哪些业务逻辑触发的。这可以包括数据库更新、后端服务的数据变更通知等。

  2. 监听数据的更新事件:在数据被修改、更新或者删除时,需要能够捕捉到这些事件。这可以通过数据库的触发器(Trigger)机制、消息队列、发布-订阅模式等方式来实现。目的是在数据更新时能够及时感知到。

  3. 更新缓存和设置逻辑过期时间:当接收到数据更新事件时,需要更新相应的缓存,并重新设置逻辑过期时间。这意味着需要将最新的数据加载到缓存中,并根据业务规则设置适当的过期时间。这可以通过缓存服务的API或者命令来完成。

  4. 缓存访问时的逻辑判断:在每次访问缓存之前,需要进行逻辑判断以确定数据是否过期。这可以基于缓存中存储的逻辑过期时间和当前时间进行比较。如果数据已经过期,需要重新加载最新数据到缓存中。

  5. 数据加载的并发控制:在数据过期时,可能会有多个线程同时检测到数据过期并尝试重新加载数据到缓存。为了避免并发查询对后端服务造成压力,可以使用互斥锁或其他并发控制机制,确保只有一个线程负责重新加载数据,其他线程从缓存中获取旧数据。

  6. 定期刷新数据:除了根据数据更新事件进行缓存更新外,还可以定期刷新数据来保持缓存的新鲜性。定期刷新可以通过设置一个时间间隔,在规定的时间间隔内重新加载数据到缓存中,避免数据长时间未更新而导致的过期数据。

代码实现

首先

我们先写一个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;

}
03-28 14:55