Redis框架(八):大众点评项目 逻辑过期时间处理缓存击穿

大众点评项目 逻辑过期时间处理缓存击穿

SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则

  1. 基础+实战的Demo和Coding上传到我的代码仓库
  2. 在原有基础上加入一些设计模式,stream+lamdba等新的糖
  3. 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式

代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Redis优化-链接: RedisThreeStrategiesProject

需求:逻辑过期时间处理缓存击穿

在这里插入图片描述

上一节已经讲了下互斥锁
互斥锁本身是ok的,但是将当前资源锁住,后面的用户 只能查询等待
有没有更好的解决方式那?
这里可以设计逻辑过期时间

我们定义一个逻辑过期时间

  1. 当用户A过来,发现过期,通过互斥锁去更新Redis
  2. 这个期间,其他用户过来,可以异步去获得旧数据,而不是一直等待
  3. 这里就需要我们手动设置一个过期时间,定义相应的RedisData类,封装过期时间信息

在这里插入图片描述

业务实现

在这里插入图片描述

  1. 更新RedisData,获取过期时间,这里的时间是手动设置的
@Data
public class RedisData {

    private LocalDateTime expireTime;
    private Object object;
}

  1. 去单元测试中测试,向Redis注入热点信息
    public void saveShop2Redis(Long id, Long expireSeconds){
        Shop shop = getById(id);

        RedisData data = new RedisData();

        data.setObject(shop);
        data.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));

        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(data));

    }
@SpringBootTest
class HmDianPingApplicationTests {

    @Resource
    private ShopServiceImpl shopService;

    @Test
    void testSaveShop(){

        for (int i = 1; i < 15; i++) {
            Long value = Long.valueOf(i);
            shopService.saveShop2Redis(value, 10L);
        }

    }

}
  1. 业务逻辑代码,个人写的,没有使用异步,本质上还是互斥锁
    //逻辑过期  个人写  并不能实现异步 还是阻塞
    public Result queryById2(Long id) {

        String key = RedisConstants.CACHE_SHOP_KEY + id;
        String shopStr = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(shopStr)){
            return Result.fail("NULL");
        }


        RedisData data = JSONUtil.toBean(shopStr, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);


        if(data.getExpireTime().isAfter(LocalDateTime.now())){
            return Result.ok(shop);
        }


        //过期了,就通过独立线程 -> 互斥锁处理缓存击穿
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        try {
            if (!tryLock(lockKey)) {
                Thread.sleep(50);
                return queryById(id);
            }

            shop = getById(id);
            log.debug("lockKey: " + lockKey + " shopByDB " + shop);

            if (shop == null) {
                return Result.fail("店铺类型不存在!");
            }
            data.setExpireTime(LocalDateTime.now().plusSeconds(3600));
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(data),   RedisConstants.CACHE_NULL_TTL, TimeUnit.HOURS);


        } catch (InterruptedException e) {
            throw new RuntimeException(e);
            /*e.printStackTrace();*/
        }finally {
            //删除暂时的key, 释放互斥锁
            stringRedisTemplate.delete(lockKey);
        }
        return Result.ok(shop);
    }

  1. 业务逻辑,已经更新,线程池引入,异步处理
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    @Override //逻辑过期时间 通过线程池进行处理
    public Result queryById(Long id) {
        //这里就是所有的热点数据保存到了Redis,直接去拿,热点数据没有TTL,只有过期时间这一字段
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        String shopStr = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(shopStr)){
            return Result.fail("店铺资格过期");
        }

        RedisData data = JSONUtil.toBean(shopStr, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) data.getObject(), Shop.class);

        if(data.getExpireTime().isAfter(LocalDateTime.now())){
            return Result.ok(shop);
        }

        //过期了,就通过独立线程 -> 互斥锁处理缓存击穿
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        if (!tryLock(lockKey)) {
            log.debug("lockKey: " + lockKey + " shopByDB " + shop);
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    this.saveShop2Redis(id, 10L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                    /*e.printStackTrace();*/
                }finally {
                    //删除暂时的key, 释放互斥锁
                    stringRedisTemplate.delete(lockKey);
                }
            });
        }

        if(shop==null){
            return Result.fail("店铺不存在");
        }

        return Result.ok(shop);
    }

总结

在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,下面是解决缓存击穿问题的逻辑过期代码: ``` public class RedisUtil { private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class); private static final Long SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; private static final String LOCK_PREFIX = "redis_lock_"; private static final String OK = "OK"; private static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } private JedisPool jedisPool; public RedisUtil(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 获取分布式锁 * * @param lockKey 锁 * @param requestId 请求标识 * @param expireTime 超期时间 * @return 是否获取成功 */ public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) { Jedis jedis = jedisPool.getResource(); String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); jedis.close(); return OK.equals(result); } /** * 释放分布式锁 * * @param lockKey 锁 * @param requestId 请求标识 * @return 是否释放成功 */ public boolean releaseDistributedLock(String lockKey, String requestId) { Jedis jedis = jedisPool.getResource(); Object result = jedis.eval(UNLOCK_LUA, Collections.singletonList(lockKey), Collections.singletonList(requestId)); jedis.close(); return SUCCESS.equals(result); } /** * 获取缓存 * * @param key 缓存key * @return 缓存值 */ public String get(String key) { Jedis jedis = jedisPool.getResource(); String value = jedis.get(key); jedis.close(); return value; } /** * 设置缓存 * * @param key 缓存key * @param value 缓存值 * @return 是否设置成功 */ public boolean set(String key, String value) { Jedis jedis = jedisPool.getResource(); String result = jedis.set(key, value); jedis.close(); return OK.equals(result); } /** * 设置缓存并带过期时间 * * @param key 缓存key * @param value 缓存值 * @param expireTime 过期时间,单位秒 * @return 是否设置成功 */ public boolean setWithExpireTime(String key, String value, int expireTime) { Jedis jedis = jedisPool.getResource(); String result = jedis.setex(key, expireTime, value); jedis.close(); return OK.equals(result); } /** * 缓存逻辑过期 * * @param key 缓存key * @param expireSeconds 过期时间,单位秒 * @param getDataFunc 获取数据的函数 * @return 缓存值 */ public String getOrSetWithExpireLogic(String key, int expireSeconds, Supplier<String> getDataFunc) { String value = get(key); if (value == null) { // 获取分布式锁 String requestId = UUID.randomUUID().toString(); boolean lockResult = tryGetDistributedLock(LOCK_PREFIX + key, requestId, expireSeconds * 1000); if (lockResult) { // 获取数据 value = getDataFunc.get(); if (value != null) { // 设置缓存并带过期时间 setWithExpireTime(key, value, expireSeconds); logger.info("set cache success, key={}, expireSeconds={}", key, expireSeconds); } // 释放分布式锁 releaseDistributedLock(LOCK_PREFIX + key, requestId); } else { // 获取锁失败,等待一段时间后重试 try { Thread.sleep(100); } catch (InterruptedException e) { logger.error("线程等待异常", e); } // 递归调用自身 return getOrSetWithExpireLogic(key, expireSeconds, getDataFunc); } } return value; } } ``` 这段代码实现了一个缓存逻辑过期的功能。当缓存失效时,先获取分布式锁,然后再次检查缓存是否存在,如果不存在,则执行获取数据的函数,然后设置缓存并带过期时间,最后释放分布式锁。如果获取分布式锁失败,则等待一段时间后重试。这样可以避免缓存击穿的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值