专栏首页慕容千语的架构笔记缓存一致性策略以及雪崩、穿透问题

缓存一致性策略以及雪崩、穿透问题

一. 缓存原理

高并发情境下首先考虑到的第一层优化方案就是增加缓存,尤其是通过Redis将原本在数据库中的数据复制一份放到内存中,可以减少对数据库的读操作,数据库的压力降低,同时也会加快系统的响应速度,但是同样的也会带来其他的问题,比如需要考虑数据的一致性、还需要预防可能的缓存击穿、穿透和雪崩问题等等。

1. 实现步骤

先查询缓存中有没有要的数据,如果有,就直接返回缓存中的数据。如果缓存中没有要的数据,才去查询数据库,将得到数据更新到缓存再返回,如果数据库中也没有就可以返回空。

考虑数据一致性,缓存处的代码逻辑都较为标准化,首先取Redis,击中则返回,未击中则通过数据库来进行查询和同步。

public Result query(String id) {
    Result result = null;
    //1.从Redis缓存中取数据
    result = (Result)redisTemplate.opsForValue().get(id);
    if (null != result) {
        System.out.println("缓存中得到数据");
        return result;
    }
    //2.通过DB查询,有则同步更新redis,否则返回空
    System.out.println("数据库中得到数据");
    result = Dao.query(id);
    if (null != result) {
        redisTemplate.opsForValue().set(id,result);
        redisTemplate.expire(id,20000, TimeUnit.MILLISECONDS);
    }
    return result;
}

其他的新增、删除和更新操作,可以直接采用先清空该Key下的缓存值再进行DB操作,这样逻辑清晰简单,维护的复杂度会降低,而付出代价就是多查询一次。

public void update(Entity entity) {
    redisTemplate.delete(entity.getId());
    Dao.update(entity);
    return entity;
}

public Entity add(Entity entity) {
    redisTemplate.delete(entity.getId());
    Dao.insert(entity);
    return entity;
}

2. 缓存更新策略

适用于做缓存的场景一般都是:访问频繁、读场景较多而写场景少、对数据一致性要求不高。如果上面三个条件都不符合,那维护一套缓存数据的意义并不大了,实际应用中通常都需要针对业务场景来选择合适的缓存方案,下面给出了四种缓存策略,由上到下就是按照一致性由强到弱的顺序。

更新策略特点适用场景

实时更新同步更新保证强一致性,与业务强侵入强耦合金融转账业务等

弱实时异步更新(MQ/发布订阅/观察者模式),业务解耦,弱一致性存在延迟不适合写频繁场景

失效机制设置缓存失效,有一定延迟,可能存在雪崩适用读多写少,能接受一定的延时

任务调度通过定时任务进行全量更新统计类业务,访问频繁且定期更新

二. 缓存雪崩和击穿

1. 缓存雪崩概念

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

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

用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。

第一种方案比较容易实现,第二种的思路主要是从加阻塞式的排它锁来实现,在缓存查询不到的情况下,每此只允许一个线程去查询DB,这样可避免同一个ID的大量并发请求都落到数据库中。

public Result query(String id) {
    // 1.从缓存中取数据
    Result result = null;
    result = (Result)redisTemplate.opsForValue().get(id);
    if (result ! = null) {
        logger.info("缓存中得到数据");
        return result;
    }

    //2.加锁排队,阻塞式锁
    doLock(id);//多少个id就可能有多少把锁
    try {
        //一次只有一个线程
       //双重校验,第一次获取到后面的都可以从缓存中直接击中
        result = (Result)redisTemplate.opsForValue().get(id);
        if (result != null) {
            logger.info("缓存中得到数据");
            return result;//第二个线程,这里返回
        }

        result = dao.query(id);
        // 3.从数据库查询的结果不为空,则把数据放入缓存中,方便下次查询
        if (null != result) {
            redisTemplate.opsForValue().set(id,result);
            redisTemplate.expire(id,20000, TimeUnit.MILLISECONDS);
        }
        return provinces;
    } catch(Exception e) {
        return null;
    }
    finally {
        //4.解锁
        releaseLock(provinceid);
    }
}

private void releaseLock(String userCode) {
    ReentrantLock oldLock = (ReentrantLock) locks.get(userCode);
    if(oldLock !=null && oldLock.isHeldByCurrentThread()) {
        oldLock.unlock();
    }
}

private void doLock(String lockcode) {
    //id有不同的值
    //id相同的,加一个锁,不是同一个key,不能用同一个锁
    ReentrantLock newLock = new ReentrantLock();//创建一个锁
    //若已存在,则newLock直接丢弃
    Lock oldLock = locks.putIfAbsent(lockcode, newLock);
    if(oldLock == null) {
        newLock.lock();
    } else {
        oldLock.lock();
    }
}

注意:加锁排队的解决方式在处理分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!

2. 缓存击穿概念

一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

解决方案

在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

三. 缓存穿透

1. 缓存穿透概念

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

解决方案:布隆过滤器

布隆过滤器的使用方法,类似java的SET集合,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。

使用步骤:

将List数据装载入布隆过滤器中

private BloomFilter<String> bf =null;

//PostConstruct注解对象创建后,自动调用本方法
@PostConstruct
public void init() {
    //在bean初始化完成后,实例化bloomFilter,并加载数据
    List<Entity> entities= initList();
    //初始化布隆过滤器
    bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), entities.size());
    for (Entity entity : entities) {
        bf.put(entity.getId());
    }
}

访问经过布隆过滤器,存在才可以往db中查询

public Provinces query(String id) {
    //先判断布隆过滤器中是否存在该值,值存在才允许访问缓存和数据库
    if(!bf.mightContain(id)) {
        Log.info("非法访问"+System.currentTimeMillis());
        return null;
    }
    Log.info("数据库中得到数据"+System.currentTimeMillis());
    Entity entity= super.query(id);
    return entity;
}

这样当外界有恶意攻击时,不存在的数据请求就可以直接拦截在过滤器层,而不会影响到底层数据库系统。

本文分享自微信公众号 - 慕容千语(mrqy_1507153110),作者:如若

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 缓存淘汰、缓存穿透、缓存击穿、缓存雪崩、数据库缓存双写一致性

    为什么需要缓存淘汰?你需要缓存30G的数据,但是Redis本身只能使用10G的内存,那你就得做个取舍了,毕竟鱼与熊掌不可兼得。为了利益最大化肯定要保留最重要的1...

    Java学习录
  • 缓存穿透、缓存并发、缓存雪崩、缓存抖动、热点缓存、缓存双写一致性等问题

    对于高并发的业务场景,常用的技术手段包括黑白名单、限流防刷、熔断降级、兜底、线程隔离、多级缓存(客户端、CDN、NGINX、内存缓存、分布式缓存)等等。这些手段...

    码农编程进阶笔记
  • Redis-缓存雪崩、缓存击穿、缓存穿透

    Redis作为目前使用最广泛的缓存,相信大家都不陌生。但是使用缓存并没有这么简单,还要考虑缓存雪崩,缓存击穿,缓存穿透的问题,什么是缓存雪崩,击穿,穿透呢,出现...

    java技术爱好者
  • 缓存在高并发场景下的常见问题

    丁浪,阿里影业架构师,社区活跃人士。有分布式事务,分布式缓存方面的总结文章获得好评。 本文获作者授权发布。 缓存一致性问题 当数据时效性要求很高时,需要保证缓存...

    企鹅号小编
  • 【实战问题】-- 缓存穿透,缓存击穿和缓存雪崩的区别以及解决方案

    平时我们使用缓存的方案,一般是在数据库中存储一份,在缓存中同步存储一份。当请求过来的视乎,可以先从缓存中取数据,如果有数据,直接返回缓存中的结果。如果缓存中没有...

    秦怀杂货店
  • 再也不怕,缓存雪崩、击穿、穿透!

    用户的数据一般都是存储于数据库,数据库的数据是落在磁盘上的,磁盘的读写速度可以说是计算机里最慢的硬件了。

    小林coding
  • 大厂面试系列(六):Redis及nosql应用

    zhaozhen
  • 这几道Redis面试题都不懂,怎么拿offer?

    在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键...

    美的让人心动
  • 这几道 Redis 面试题都不懂,怎么拿 Offer?

    随着系统访问量的提高,复杂度的提升,响应性能成为一个重点的关注点。而缓存的使用成为一个重点。redis 作为缓存中间件的一个佼佼者,成为了面试必问项目。

    芋道源码
  • 面试前必须要知道的Redis面试题

    在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键...

    Java团长
  • 面试前必须要知道的Redis面试题

    在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键...

    Java3y
  • 面试前必须要知道的Redis面试题

    在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键...

    好好学java
  • 微博宕机背后,高并发有哪些常见问题?

    上周,赵丽颖、冯绍峰结婚官宣造成微博宕机,那么回归技术,面对如此大的高并发流量和屡次崩溃的系统,作为程序员是否有较好的方法来应对及解决?在此,技术专家丁浪为我们...

    CSDN技术头条
  • 面试前必须要知道的Redis面试题

    在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键...

    帅地
  • 高并发场景下的缓存有哪些常见的问题?

    当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。

    Java团长
  • 高并发场景下的缓存有哪些常见的问题?

    当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。

    用户5224393
  • 缓存在高并发场景下的常见问题

    当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象。这就比较依赖缓存的过期和更新策...

    Bug开发工程师
  • Redis缓存雪崩、缓存穿透、热点Key解决方案和分析

    转载自  https://blog.csdn.net/wang0112233/article/details/79558612

    allsmallpig
  • 缓存穿透,缓存击穿,缓存雪崩的内容和解决方案

    每次想到缓存的概念时就会想到下面这张结构图,缓存主要解决的是中央处理器与内存之间速度不匹配出来的问题。

    码农王同学

扫码关注云+社区

领取腾讯云代金券