前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis缓存击穿

redis缓存击穿

作者头像
roydonGuo
发布2022-11-02 15:39:24
5350
发布2022-11-02 15:39:24
举报
文章被收录于专栏:postsposts

场景

也是热点key问题,就是一个高并发访问并且缓存重建较为复杂的key突然失效了,这里的key失效可以理解为某电商平台在节日大促,同时段大量请求访问一个商品,这个商品key会存在一个固定TTL,若TTL到时了,key消失,仍有大量请求访问该商品,这个key的重建业务复杂,耗时又高。于是,请求都来到数据库拿数据,瞬间给数据库造成了巨大的压力。

解决方案

互斥锁

在发起请求未命中redis缓存时,表示此信息不存在,或过期,尝试获取锁。 若没拿到锁,表示此数据正在被更新,线程进行休眠再递归重新从缓存获取数据。 若拿到了锁,根据id查数据库,将数据信息写入redis并释放互斥锁,返回数据。

互斥锁
互斥锁

ps:图片来自B站黑马程序员

redis中有命令setnx key value表示key不存在就set,存在就无法赋值,这就可以应用到锁中

在这里插入图片描述
在这里插入图片描述
nx
nx

对应方法setIfAbsent(),再设置过期时间,避免死锁

代码语言:javascript
复制
private boolean tryLock(String key) {//获取锁
        Boolean flag = stringRedisTemplate
        					.opsForValue()
        					.setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        					//锁的存在时间,几秒就行
        return BooleanUtil.isTrue(flag);//flag可能为null,进行拆箱封箱
    }

模拟:封装在了queryWithMutex()中,开始店铺信息不存在redis中,当请求需要获取店铺信息时,redis没命中,那么获取锁, 进行缓存写入操作,成功后释放锁并返回数据。然后其他请求就能从redis中拿到数据了。 ps:其中有部分缓存穿透操作,请前往redis缓存穿透

代码语言:javascript
复制
	@Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        //缓存穿透
//        Shop shop = queryWithPassThrough(id);
        //互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if (shop == null) {
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }

    public Shop queryWithMutex(Long id) {
        //从redis查询商铺缓存
        String key = CACHE_SHOP_KEY + id;//缓存的key
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)) {
            //存在直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否为空值
        if (shopJson != null) {//shopJson==""
            //返回一个错误信息
            return null;
        }
        //4.redis中不存在,实现缓存重建
        //4.1获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2判断是否获取成功
            if (!isLock) {//获取🔒失败
                //4.3失败,休眠小时间后重试查询操作(递归)
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //4.4成功,根据id查询数据库
            shop = getById(id);
            Thread.sleep(200);//模拟数据库不在本机,查询数据库会有延时
            if (shop == null) {//数据库没有数据,缓存设空
                //将null写入redis(缓存穿透策略)
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                return null;
            }
            //数据库中存在,存入redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unLock(lockKey);
        }

        return shop;
    }

    private boolean tryLock(String key) {//获取锁
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);//时间很短
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key) {//释放锁
        stringRedisTemplate.delete(key);//就是删除缓存的锁
    }

锁最终需要被释放,所以try的finally需要释放锁。 理论预期,只会进行一次数据库查询,需要在高并发条件模拟大量请求到来,只有一个线程获取锁,写缓存,其他请求递归查询,等待,直到写入缓存成功,理论全部请求会通过,只是耗时问题)

redis无缓存,大量请求到来,数据库查询次数一次,并存入redis,释放锁

在这里插入图片描述
在这里插入图片描述

逻辑过期

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景
  • 解决方案
    • 互斥锁
      • 逻辑过期
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档