缓存击穿

概念

缓存击穿问题也叫热点key问题,指的是一个被高并发访问并且缓存重建业务较为复杂的key突然失效了,大量的请求会到达数据库给数据库带来巨大的冲击。

常见解决方法有两种:互斥锁,逻辑过期

优缺点 :

基于互斥锁的业务代码改造

java 复制代码
ublic Shop queryWithMutex(Long id) {
        //先从redis中查询缓存
        try {
            String object = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY);
            //如果有直接返回
            if (StrUtil.isNotBlank(object)) {
                //转成Java对象
                Shop bean = JSONUtil.toBean(object, Shop.class);
                return bean;
            }
            //判断命中的是否是空值
            if (object != null) {
                return null;
            }
            //实现缓存重建
            //尝试获取锁
            boolean trylock = trylock(LOCK_SHOP_KEY);
            if (!trylock) {
                //如果获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //如果没有,查询数据库
            Shop shop = this.getById(id);
            //如果数据库中没有,报错
            if (shop == null) {
                //return Result.fail("没有该店铺");
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //如果有,写入redis
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY, JSONUtil.toJsonStr(shop), 30, TimeUnit.MINUTES);
            return shop;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //关闭互斥锁
            unlock(LOCK_SHOP_KEY);
        }
    }



//获取锁和释放锁 (基于redis的setNX实现)

 private boolean trylock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    //释放锁
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

基于逻辑过期的业务代码改造

我们需要新建一个对象来封装我们的店铺信息和逻辑过期时间,所以从redis中查询到数据后需要手动反序列化程Java对象获取我们的逻辑过期时间和店铺信息。

java 复制代码
  private static final ExecutorService CACHE_REBUILD_POOL = Executors.newFixedThreadPool(10);



public Shop queryWithLogicExpire(Long id) {
        //先从redis中查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY);
        //如果有直接返回
        if (StrUtil.isBlank(shopJson)) {
            //如果不存在,直接返回
            return null;
        }
        //命中,需要把json反序列化转成java对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        JSONObject shop = (JSONObject) redisData.getData();
        Shop shopBean = JSONUtil.toBean(shopJson, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())) {
             //未过期,返回旧数据
            return shopBean;
        }
        //过期,需要缓存重建
        //获取互斥锁
        boolean islock = trylock(LOCK_SHOP_KEY + id);
        if (islock) {
            //如果获取成功,开启线程实现缓存重建
            //此处应该再次判断逻辑时间是否过期
            try {

                CACHE_REBUILD_POOL.submit(() -> {
                    this.saveShop2Redis(id,20l);

            });
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                unlock(LOCK_SHOP_KEY + id);
            }
        }
        //返回旧数据
        return shopBean;


    }


//缓存重建
  public void saveShop2Redis(Long id, Long expierSecond) {
        //查询店铺信息
        Shop shopById = getById(id);
        //封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shopById);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expierSecond));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY +id + shopById.getId(), JSONUtil.toJsonStr(redisData), 30, TimeUnit.MINUTES);
    }



//获取锁
 private boolean trylock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    //释放锁
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
相关推荐
玄芯散人8 小时前
【筑基·047】内存Hierarchy:寄存器到硬盘的速度差
缓存·内存·计算机基础
momo061178 小时前
Redis新手入门 -- 学习笔记
redis·后端
xhbh66610 小时前
中小企业 Redis 运维,如何安全归档 RDB、AOF 备份文件?
运维·数据库·缓存·数据备份·文件备份·同步备份·号码备份
吃饱了得干活16 小时前
Redisson 进阶实战:看门狗机制与分布式锁完全指南
java·redis·后端
Wang's Blog18 小时前
Java框架快速入门: Spring Security+OAuth2之多因子认证缓存服务
java·spring·缓存
雪芽蓝域zzs18 小时前
第三十六节:keep-alive 页面缓存(Vue3 + 后台管理系统)
前端·vue.js·缓存
我是苏苏18 小时前
Redis开发09:Windows系统下主从复制的配置教程
数据库·redis·缓存
写后端的胖头鱼21 小时前
【高频面试题】如何找出redis中的慢查询记录
redis·慢日志
mengzhi啊1 天前
缓存类CacheDataManager把临时数据存入json。做成插件·
qt·缓存·json
彧azz1 天前
Redis 全面指南:从核心概念到高可用架构实战
数据库·redis·架构