【大型电商项目开发】缓存的击穿,穿透,雪崩-加锁解决缓存击穿-42

一:缓存失效问题

1.缓存穿透——查一个不存在的数据

概念:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。

风险:在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,数据库瞬时压力增大,最终导致崩溃,这就是漏洞。

解决:把查询出来的null结果同时加入缓存,并且对查询出来的空结果加入短暂的过期时间。三分钟以后就会清除掉空结果,重新查询数据库,为了防止从缓存查出来的数据一直为空。

2.缓存雪崩——存储的数据在同一时间大面积失效

概念:缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。

解决:原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的 重复率就会降低,就很难引发集体失效的事件。

3.缓存击穿——热点key在高并发的时候过期

概念:对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问, 是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所 有对这个 key 的数据查询都落到 db,我们称为缓存击穿。

解决:加锁,大量并发只让一个去查,其他人等待,查到结果以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去数据库查询。

二:加锁解决缓存击穿

1.单体锁——服务只部署在一台服务器上

拿到锁的第一时间,先看看缓存里面有没有,有可能上一个人已经放好了。如果没有再去查询数据库

   /**
     * 查出所有分类,按照要求返回,从数据库查询并且封装数据
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDb() {
        synchronized (this){
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if(!StringUtils.isEmpty(catalogJson)){
                TypeReference<Map<String, List<Catalog2Vo>>> typeReference = new TypeReference<Map<String, List<Catalog2Vo>>>() {};
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson,typeReference);
                return result;
            }
            //从数据库获取三级分类方法,此刻不做演示
        }
    }
  • 只要是同一把锁就能锁住这个锁的所有线程
  • synchronized (this):springboot所有的组件在容器中都是单例的,只要所有请求用同一个this就能锁住
  • this代表当前对象,有多少台服务器就会有多少个this对象
  • synchronized 和lock都是本地锁,只能所住当前进程
    在这里插入图片描述

分析:分布式情况下每个服务器this对象不一样,有多少服务器就会有多少人请求访问进来,所以此时我们每个this对象代表不同的锁,所以此时我们想要锁住所有,必须要使用分布式锁。

2.单体锁时序问题

在这里插入图片描述
流程:
1)当第一个线程查询缓存没有数据时,就会查询数据库,然后将查询到的结果放进缓存。
2)此时第二个缓存进来,第一次查询的结果还没有放进缓存,因为放进缓存的时间挺久的,就会导致第二次线程仍然会查询数据库,就没有锁住。
3)所以我们要在放进缓存以后,再去让第三个线程进来。
代码实现:

  /**
     * 查出所有分类,按照要求返回,从数据库查询并且封装数据
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDb() {
        synchronized (this){
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if(!StringUtils.isEmpty(catalogJson)){
                TypeReference<Map<String, List<Catalog2Vo>>> typeReference = new TypeReference<Map<String, List<Catalog2Vo>>>() {};
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson,typeReference);
                return result;
            }
            //从数据库获取三级分类方法,此刻不做演示
            Object entity = fromDb();
            String s = JSON.toJSONString(entity);
            redisTemplate.opsForValue().set("catalogJson",s,1, TimeUnit.DAYS);
            return entity;
        }
    }

3.本地锁在分布式情况下会出现的问题

模拟多个商品服务:在本地我们将商品服务多复制几份,每个商品服务端口不一样,用来进行测试。
结果:每个服务都会查询一次数据库,此时我们需要使用分布式锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随意石光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值