六、缓存方案实战

一、缓存读取数据

在这里插入图片描述

public Provinces detail(String provinceid) {
        Provinces provinces = null;

        //在redis查询
        provinces = (Provinces)redisTemplate.opsForValue().get(provinceid);
        if (null != provinces){
//            redisTemplate.expire(provinceid,20000, TimeUnit.MILLISECONDS);
            System.out.println("缓存中得到数据");
            return provinces;
        }

        provinces = super.detail(provinceid);
        if (null != provinces){
            redisTemplate.opsForValue().set(provinceid,provinces);//set缓存
            redisTemplate.expire(provinceid,20000, TimeUnit.MILLISECONDS);//设置过期
        }

        return provinces;
    }

二、缓存一致性

1、缓存该淘汰还是更改

1.1更新缓存存在的问题
  1. 如果value存储的是json格式的object,更新时需要将数据从redis读取出来,再进行相关字段的设置,有可能还要进行相关计算,再进行set回redis。增加了应用进程和redis的交互次数
  2. 并发写会存在数据不一致问题
    不论是先操作数据库还是先操作缓存,都存在一个问题:分布式不能保证顺序性。
    在这里插入图片描述
    先set缓存,再更新数据库。
    1.1.set缓存
    2.1、set缓存、更新数据库
    1.2、更新数据库
    结论:数据库中的数据是旧数据
    先更新数据库再操作缓存同理。

2、先操作数据库还是先淘汰缓存

2.1 先操作缓存,再操作数据库

存在的问题、并发读写存在数据不一致问题
在这里插入图片描述

  • 请求 A 进行写操作(key = 1 value = 2),先删除缓存 key = 1 value = 1
  • 请求 B 查询发现缓存不存在
  • 请求 B 去数据库查询得到旧值 key = 1 value = 1
  • 请求 B 将旧值写入缓存 key = 1 value = 1
  • 请求 A 将新值写入数据库 key = 1 value = 2
    缓存中数据永远都是脏数据

建议:先删除缓存,再更新数据库,再删缓存(双删,第二次删可异步延时)

2.2 先更新数据库再淘汰缓存

当程序原子性被破坏时,会导致数据不一致。

  • 更细数据库成功
  • 淘汰缓存失败
    导致缓存中的数据时旧数据
    如果是先淘汰缓存,再删除数据库,原子性被破坏时,数据都是旧值

建议:先淘汰缓存、再更新数据库
1、采用双删除记录
2、监控数据库从库的binlog,更新缓存

三、缓存击穿、雪崩

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于 并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力 瞬间增大,造成过大压力。
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压 力过大甚至 down 机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩 是不同数据都过期了,很多数据都查不到从而查数据库。

对mysql的操作进行加锁操作

public Provinces detail(String provinceid) {
        // 1.从缓存中取数据
        Cache.ValueWrapper valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);
        if (valueWrapper != null) {
            logger.info("缓存中得到数据");
            return (Provinces) (valueWrapper.get());
        }

        //2.加锁排队,阻塞式锁---100个线程走到这里---同一个sql的取同一把锁
        doLock(provinceid);//32个省,最多只有32把锁,1000个线程
        try{//第二个线程进来了
            // 一次只有一个线程
             //双重校验,不加也没关系,无非是多刷几次库
            valueWrapper = cm.getCache(CACHE_NAME).get(provinceid);//第二个线程,能从缓存里拿到值?
            if (valueWrapper != null) {
                logger.info("缓存中得到数据");
                return (Provinces) (valueWrapper.get());//第二个线程,这里返回
            }

            Provinces provinces = super.detail(provinceid);
            // 3.从数据库查询的结果不为空,则把数据放入缓存中,方便下次查询
            if (null != provinces){
                cm.getCache(CACHE_NAME).put(provinceid, provinces);
            }
            return provinces;
        }catch(Exception e){
            return null;
        }finally{
            //4.解锁
            releaseLock(provinceid);
        }
    }


private void doLock(String lockcode) {//给一个搜索条件,对应一个锁
        //provinceid有不同的值,参数多样化
        //provinceid相同的,加一个锁,---- 不是同一个key,不能用同一个锁
        ReentrantLock newLock = new ReentrantLock();//创建一个锁
        Lock oldLock = locks.putIfAbsent(lockcode, newLock);//若已存在,则newLock直接丢弃
        if(oldLock == null){
            newLock.lock();
        }else{
            oldLock.lock();
        }
    }

四、缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为 id 为“-1”的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导 致数据库压力过大。

解决方案:使用布隆过滤器
在这里插入图片描述

初始化布隆过滤器

@PostConstruct //对象创建后,自动调用本方法
    public void init(){//在bean初始化完成后,实例化bloomFilter,并加载数据
        List<Provinces> provinces = this.list();

        //当成一个SET----- 占内存,比hashset占得小很多
        bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), provinces.size());// 32个
        for (Provinces p : provinces) {
            bf.put(p.getProvinceid());
        }
    }

过滤请求

@Cacheable(value = "province")
    public Provinces detail(String provinceid) {
        //先判断布隆过滤器中是否存在该值,值存在才允许访问缓存和数据库
        if(!bf.mightContain(provinceid)){
            System.out.println("非法访问--------"+System.currentTimeMillis());
            return null;
        }
        System.out.println("数据库中得到数据--------"+System.currentTimeMillis());
        Provinces provinces = super.detail(provinceid);

        return provinces;
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值