当前位置: 首页> 新闻> 焦点 > 蜗牛影院看电影_北京网页设计师培训班_千锋教育_seo点击工具

蜗牛影院看电影_北京网页设计师培训班_千锋教育_seo点击工具

时间:2025/8/27 16:31:46来源:https://blog.csdn.net/Tong_Hao/article/details/145504114 浏览次数:0次
蜗牛影院看电影_北京网页设计师培训班_千锋教育_seo点击工具

业务背景

当一个热点优惠券不在缓存中或缓存过期时,可能会有大量用户同时请求访问,这就引发了缓存击穿问题

什么是缓存击穿?

缓存击穿指在高并发的系统中,一个热点数据缓存过期或者在缓存中不存在,导致大量并发请求直接访问数据库,从而给数据库造成巨大压力,甚至可能引起宕机。

具体来说,当某个热点数据在缓存中过期时,如果此时有大量并发请求同时访问这个数据,由于缓存中不存在,所有请求都会直接访问数据库,导致数据库负载急剧增加。

缓存击穿解决方案

一般来说,我们会通过预热和缓存永不过期的机制让缓存不击穿,这样即使再大的流量也可以通过缓存去抗。

  • 缓存预热:热点数据预加载,指的是在活动或者大促开始前,针对已知的热点数据从数据库加载到缓存中,这样可以避免海量请求第一次访问热点数据需要从数据库读取的流程。

  • 永不过期:热点数据永不过期,指的就是可以预知的热点数据,在活动开始前,设置过期时间为 -1。这样的话,就不会有缓存击穿的风险。

    上面两个一般都是搭配一起使用的。等对应热点缓存的活动结束后,这些数据访问量就比较低了,可以通过后台任务的方案对指定缓存设置过期时间,这样可以有效降低 Redis 存储压力。

    分布式锁解决方案

    分布式锁的解决方案就是保证只有一个请求可以访问数据库,其它请求等待结果。这样可以避免大量的请求同时访问数据库。

    但是这种的话有一个弊端,那就是获取分布式锁的请求,都会执行一遍查询数据库,并更新到缓存。理论上只有第一个加载数据库记录请求是有效的

    所以需要继续改进

    双重判定锁

    在获取到分布式锁之后,再次查询一次缓存是否存在。如果缓存中存在数据,就直接返回;如果不存在,才继续执行查询数据库的操作。这样就可以避免大量请求访问数据库。

    伪代码如下:

    public String selectTrain(String id) {// 查询缓存不存在,去数据库查询并放入到缓存String cacheData = cache.get(id);if (StrUtil.isBlank(cacheData)) {// 为避免大量请求同时访问数据库,通过分布式锁减少数据库访问量Lock lock = getLock(id);lock.lock();try {// 获取锁后双重判定cacheData = cache.get(id);// 理论上只有第一个请求加载数据库是有效的,因为它加载后会把数据放到缓存// 后面的请求再请求数据库加载缓存就没有必要了if (StrUtil.isBlank(cacheData)) {// 获取数据库中存在的数据String dbData = trainMapper.selectId(id);if (StrUtil.isNotBlank(dbData)) {// 将查询到的数据放入缓存,下次查询就有数据了cahce.set(id, dbData);cacheData = dbData;}}} finally {lock.unlock();}}return cacheData;
    }

     

    下面是这种场景下解决方案的一般步骤:

    • 获取锁:在查询数据库前,首先尝试获取一个分布式锁。只有一个线程能够成功获取锁,其他线程需要等待;
    • 查询数据库:如果双重判断确认数据确实不存在于缓存中,那么就执行查询数据库的操作,获取数据;
    • 写入缓存:获取到数据后,将数据写入缓存,并设置一个合适的过期时间,以防止缓存永远不会被更新;
    • 释放锁:最后,释放获取的锁,以便其他线程可以继续使用这个锁。

    开发优惠券模板查询

    直接上我们最终确定的分布式锁+双重判定的方案,代码如下:

    @Override
    public CouponTemplateQueryRespDTO findCouponTemplate(CouponTemplateQueryReqDTO requestParam) {// 查询 Redis 缓存中是否存在优惠券模板信息String couponTemplateCacheKey = String.format(EngineRedisConstant.COUPON_TEMPLATE_KEY, requestParam.getCouponTemplateId());Map<Object, Object> couponTemplateCacheMap = stringRedisTemplate.opsForHash().entries(couponTemplateCacheKey);
    ​// 如果存在直接返回,不存在需要通过双重判定锁的形式读取数据库中的记录if (MapUtil.isEmpty(couponTemplateCacheMap)) {// 获取优惠券模板分布式锁RLock lock = redissonClient.getLock(String.format(EngineRedisConstant.LOCK_COUPON_TEMPLATE_KEY, requestParam.getCouponTemplateId()));lock.lock();
    ​try {// 通过双重判定锁优化大量请求无意义查询数据库couponTemplateCacheMap = stringRedisTemplate.opsForHash().entries(couponTemplateCacheKey);if (MapUtil.isEmpty(couponTemplateCacheMap)) {LambdaQueryWrapper<CouponTemplateDO> queryWrapper = Wrappers.lambdaQuery(CouponTemplateDO.class).eq(CouponTemplateDO::getShopNumber, Long.parseLong(requestParam.getShopNumber())).eq(CouponTemplateDO::getId, Long.parseLong(requestParam.getCouponTemplateId())).eq(CouponTemplateDO::getStatus, CouponTemplateStatusEnum.ACTIVE.getStatus());CouponTemplateDO couponTemplateDO = couponTemplateMapper.selectOne(queryWrapper);
    ​// 优惠券模板不存在或者已过期直接抛出异常if (couponTemplateDO == null) {throw new ClientException("优惠券模板不存在或已过期");}
    ​// 通过将数据库的记录序列化成 JSON 字符串放入 Redis 缓存CouponTemplateQueryRespDTO actualRespDTO = BeanUtil.toBean(couponTemplateDO, CouponTemplateQueryRespDTO.class);Map<String, Object> cacheTargetMap = BeanUtil.beanToMap(actualRespDTO, false, true);Map<String, String> actualCacheTargetMap = cacheTargetMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,entry -> entry.getValue() != null ? entry.getValue().toString() : ""));
    ​// 通过 LUA 脚本执行设置 Hash 数据以及设置过期时间String luaScript = "redis.call('HMSET', KEYS[1], unpack(ARGV, 1, #ARGV - 1)) " +"redis.call('EXPIREAT', KEYS[1], ARGV[#ARGV])";
    ​List<String> keys = Collections.singletonList(couponTemplateCacheKey);List<String> args = new ArrayList<>(actualCacheTargetMap.size() * 2 + 1);actualCacheTargetMap.forEach((key, value) -> {args.add(key);args.add(value);});
    ​// 优惠券活动过期时间转换为秒级别的 Unix 时间戳args.add(String.valueOf(couponTemplateDO.getValidEndTime().getTime() / 1000));
    ​// 执行 LUA 脚本stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),keys,args.toArray());couponTemplateCacheMap = cacheTargetMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));}} finally {lock.unlock();}}
    ​return BeanUtil.mapToBean(couponTemplateCacheMap, CouponTemplateQueryRespDTO.class, false, CopyOptions.create());
    }

    本节的流程图如下:

     

      关键字:蜗牛影院看电影_北京网页设计师培训班_千锋教育_seo点击工具

      版权声明:

      本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

      我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

      责任编辑: