缓存击穿及解决方案

缓存击穿及解决方案

最近小生遇到了好多人问缓存击穿的问题,作为一个乐于学习的渣渣,小生当然要去网上大肆搜集一番,综合小生的经验整理出了本篇文章,希望可以帮助到大家。

要想研究缓存击穿就得先知道什么是缓存?所谓缓存是Web应用开发同时也是互联网分层架构中非常重要的一个部分,通常用来缓解数据库的压力,从而提升系统整体性能,缩短用户的访问时间。

Web应用获取数据时先会去cache中找数据

  • cache中有数据
  • cache将数据返回给web应用
  • cache中没有数据
  • cache向数据库请求数据
  • 数据库返回数据给cache
  • cache将数据更新后返回给web应用

定义

所谓缓存击穿就是web应用去请求一个不在cache中或者已经失效的数据时,就会触发一次数据库查询,如果瞬间有大量类似的操作时就会导致数据库压力过大,造成反应过慢甚至宕机瘫痪。

解决方法

综上所述产生缓存击穿的场景主要两种

  • 同一时间访问一个或者多个不存在的数据
  • 多个数据同时失效

场景一:数据不存在

方案一:
缓存null:当通过某个key去查询时,如果cache和数据库都不存在对应的数据,那么cache可以将这个key对应的value设置为一个默认值(比如:null),并且设置一个缓存过期的时间,这样在缓存失效这段时间内,所有访问这个key的请求都会被cache拦截,等到缓存失效后再去数据库中查询。

缺点:如果不存在的数据非常多,则会存储大量的缓存垃圾,浪费资源。

方案二:
Bloomfilter:类似于一种哈希表算法,在cache之前预判这个key是否合法,如果不合法则直接返回。

缺点:存在误杀的可能性

方案三:
屏蔽IP:记录并分析访问不存在的数据请求,获取其IP地址,如果在规定时间内此IP超过某个设定值,就将它加入黑名单,在系统中进行拦截。

缺点:只针对一些黑客攻击

场景二:数据失效

方案一:
后台刷新:后台启动一个常驻进程,用于在缓存失效前主动更新缓存中的数据,在缓存失效前后台进程刷新一下缓存中的数据,保证数据永远不会过期。

缺点:会增加系统难度,比较适合那些key相对固定,cache力度较大的业务。若是key比较分散则不太合适,实现起来也比较复杂。

方案二:
get请求主动更新缓存:将缓存key的过期时间点一起保存到缓存里(这个方法有很多,拼接,添加新字段,单独key皆可),在每次执行get操作后,都将get出来的缓存过期时间和当前系统时间做一次对比,如果缓存时间减去当前系统时间小于等于某个设定值后,则主动更新缓存。这样就能保证缓存中的数据始终是最新的。

缺点:在某个特殊时刻时,例如在缓存即将过期时没有get请求,导致缓存已经过期,恰好此时有大量并发请求过来,那就惨了。当然这种情况比较极端,但也是有可能的。

方案三:
分级缓存:采用L1(一级缓存)和L2(二级缓存)的缓存方式,L1缓存失效时间短,L2缓存失效时间长。请求优先从L1获取数据,如果L1未命中则加锁,只有1个线程获取到锁,这个线程再从数据库中读取数据并将数据更新到L1和L2中,而其他线程依旧从L2缓存中获取数据并返回。

缺点:这种方式主要是通过避免缓存同时失效并结合锁机制实现。那么就会出现一个问题,有一瞬间L2可能会存在脏数据。就是当数据更新时,L1的缓存被淘汰,但是L2未被淘汰。且这种方案可能会造成额外的缓存空间浪费。

方案四:
加锁:简单粗暴,而且方法也很多。网上随随便便就可以查到,小生对锁的研究不是很深,所以就在网上筛选出了一个比较好点的方案,话不多说,上代码!!!

static Lock reenLock = new ReentrantLock();
    public List<String> getData04() throws InterruptedException {
        List<String> result = new ArrayList<String>();
        // 从缓存读取数据
        result = getDataFromCache();
        if (result.isEmpty()) {
            if (reenLock.tryLock()) {
                try {
                    System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
                    // 从数据库查询数据
                    result = getDataFromDB();
                    // 将查询到的数据写入缓存
                    setDataToCache(result);
                } finally {
                    reenLock.unlock();// 释放锁
                }

            } else {
                result = getDataFromCache();// 先查一下缓存
                if (result.isEmpty()) {
                    System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
                    Thread.sleep(100);// 小憩一会儿
                    return getData04();// 重试
                }
            }
        }
        return result;
    }

小生的分享就到这里啦,祝各位大佬工作愉快,步步高升!

发布了1 篇原创文章 · 获赞 1 · 访问量 72

猜你喜欢

转载自blog.csdn.net/weixin_45500072/article/details/104685728