首页 > 数据库 >Java-Redis缓存穿透、缓存击穿及缓存雪崩(配解决方案及代码示例)

Java-Redis缓存穿透、缓存击穿及缓存雪崩(配解决方案及代码示例)

时间:2024-07-12 14:58:54浏览次数:16  
标签:缓存 Java 示例 过期 数据库 redis 查询 key

前言

在现代高并发的互联网应用中,缓存技术已成为提升系统响应速度与减轻后端数据库压力的关键手段。Redis,以其卓越的性能和丰富的数据结构,成为众多开发者构建缓存层的首选。然而,随着业务复杂度的增加,Redis缓存层也可能遭遇“缓存穿透”、“缓存击穿”以及“缓存雪崩”等现象,这些情况不仅影响系统的稳定性和响应时间,还可能直接导致服务的不可用。本文旨在深入剖析这三种缓存异常现象,探讨其背后的原理,并提出有效的预防策略。

缓存穿透

一般的缓存系统都是通过key-value的形式来缓存数据,如果key对应的value不存在,则应该去后端系统(DB)查询,一些恶意程序会故意查询不存在的key,请求量很大,从而可能压垮数据源,这就叫-缓存穿透。比如用一个不存在的用户id去查询用户信息,此时缓存中没有该用户,就会去数据库查询。如果利用此漏洞进行攻击可能压垮数据库。

解决方案

一个一定不存在及查询不到的数据,由于缓存是不命中的时候被动进行写操作的,那么如果从DB查询不到数据,那么每次有请求进来都会从DB进行查询。所以有两种解决方案:
方案1-将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据就会被这个bitmap拦截掉,从而避免了对底层DB的压力。
方案2-对于key从DB查询的结果,如果为空也进行存储,只是过期时间设置短一点(一般不超过5分钟),这样再有同样的大数量访问进来也不会对后端造成压力。

对于方案1,常用的就是布隆过滤器(BloomFilter),而布隆过滤器实质上就是一个二进制数组和一系列的hash函数。

补充说明: 在判断布隆过滤器中是否存在时,它会进行多次hash,寻找位阵列中的值,如果多次hash的值有一个为0,那么说明不存在,如果都是1,那么也只能说明可能存在(因为可能存在hash碰撞)。解决办法:1、多次hash,2、增加二进制数组长度

那么如何创建并使用布隆过滤器呢?

1、使用redisson中的boolmFilter创建
public void patchingConsum(ConsumPatchingVO vo) throws ParseException {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress("redis://127.0.0.1:6379");
        singleServerConfig.setPassword("123456");
        RedissonClient redissonClient = Redisson.create(config);
        RBloomFilter<String> bloom = redissonClient.getBloomFilter("name");
        // 初始化布隆过滤器;  大小:100000,误判率:0.01
        bloom.tryInit(100000L, 0.01);
        // 新增10万条数据
        for(int i=0;i<100000;i++) {
        // 模拟将redis的key放入boolmfilter
            bloom.add("name" + i);
        }
        // 判断不存在于布隆过滤器中的元素
        List<String> notExistList = new ArrayList<>();
        for(int i=0;i<100000;i++) {
            String str = "name" + i;
            // 判断key在redis中是否存在
            boolean notExist = bloom.contains(str);
            if (notExist) {
                notExistList.add(str);
            }
        }
        
2、使用google的guava包
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1-jre</version>
    </dependency>
    
@Configuration
public class BloomFilterConfig {

    @Bean
    public BloomFilter<String> bloomFilter() {
        return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01);
    }
}

对于方案2,很好理解,如果查询结果为null,也存入redis中,只是将expireTime设置的小一些,具体根据业务决定,此处就不做详细说明啦。

缓存击穿

对于非常热点的数据,可能会在某些时间点被超高并发访问,那么如果在这个时间缓存失效,那么此时这并发的些高访问就会去访问数据库,这样就可能会压垮数据库。

解决方案

方案1-使用redis互斥锁
方案2-对于某一热点,将其设置为永不过期。比如秒杀的爆款,设置为永不过期。

redis互斥锁一般来讲也有两种实现方法:
1、最简单的可以使用redis中的set和delete,实现加锁和解锁,当然这种方式会有问题,例如:操作不当可能会造成锁误删。此处就不再展开,有需要以后单独发一篇介绍。
2、第二种就是redisson看门狗,可以实现自动续期。

        RLock lock = redissonClient.getLock("testLock");
        try {
            lock.lock();
            boolean tryLock = lock.tryLock(10, -1, TimeUnit.SECONDS);
            if (tryLock) {
                log.info("redisson 加锁...");
                // 业务操作...
            }
        } catch (Exception exception) {

        } finally {
            // 锁释放,防止异常时锁一直持有。
            lock.unlock();
        }

缓存雪崩

缓存雪崩和缓存击穿的区别在于缓存击穿针对的是某一个热点key,而雪崩指的是一批key在某一时间点集体失效过期,那么此时就会去数据库加载,从而会导致大量的线程对数据库进行读写从而造成数据库的瞬间压力。

解决方案

在原有的过期时间上对过期时间进行随机,这样可以减少缓存的过期时间重复率。

标签:缓存,Java,示例,过期,数据库,redis,查询,key
From: https://blog.csdn.net/weixin_43834477/article/details/140378348

相关文章

  • 【java计算机毕设】线上花店销售商城系统java MySQL ssm JSP maven项目代码源码+文档p
    目录1项目功能2项目介绍3项目地址 1项目功能【java计算机毕设】线上花店销售商城系统MySQLssmJSPmaven项目代码源码+文档PPT小组设计代码 2项目介绍系统功能:线上花卉小铺系统包括管理员、用户俩种角色。用户端:1.注册登录:游客填写基础信息,注册成为小铺用......
  • Java性能优化-switch性能优化-用String还是int做比较
    场景Java中使用JMH(JavaMicrobenchmarkHarness微基准测试框架)进行性能测试和优化:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131723751参考以上性能测试工具的使用。下面针对Java中对switch-case比较时使用String还是int性能做对比。注:博客:https://bl......
  • java设计模式(十七)状态模式(State Pattern)
    1、模式介绍:状态模式(StatePattern)是一种行为型设计模式,用于实现对象状态的变化管理。它允许一个对象在其内部状态发生变化时改变其行为,使得对象看起来似乎修改了其类。2、应用场景:当一个对象的行为取决于其状态,并且需要在运行时根据状态改变其行为时。当状态转换过程中需......
  • java设计模式(十四)策略模式(Strategy Pattern)
    1、模式介绍:策略模式是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。2、应用场景:当一个对象有多种行为,而需要动态选择一种行为时。不同的策略可以实现不同的行为,客户端根据需要在运行时选择合适的策略。当......
  • [Java]“不同族”基本数据类型间只能“强转”吗?
    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)https://www.cnblogs.com/cnb-yuchen/p/18298193出自【进步*于辰的博客】本文阐述需要计算不同位二进制的表示范围,引用博文《[MySQL]知识点》中的【数据范围通式】一栏得出的计算公式。虽然Java与MySQL属不同体系,......
  • Java性能优化-switch-case和if-else速度性能对比,到底谁快?
    场景Java中使用JMH(JavaMicrobenchmarkHarness微基准测试框架)进行性能测试和优化:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/131723751参考以上性能测试工具的使用。下面针对Java中对switch-case和if-else在速度方面的性能做测试。注:博客:https://blog......
  • java实现浅拷贝与深拷贝
    目录浅拷贝 深拷贝实现cloneable接口序列化浅拷贝浅拷贝是指创建一个新的对象,该对象的内容是原始对象中各项的引用。换句话说,浅拷贝仅复制了原始对象中元素的引用,而不是元素本身的拷贝classPeopleimplementsCloneable{privateStringname;privateint......
  • 【日常记录-Java】自定义进程池
    Author:赵志乾Date:2024-07-12Declaration:AllRightReserved!!!1.简介    服务器上有些进程需要池化管理,使用SpringBoot构建Web服务提供管理api,内部使用自定义的进程池维护已启动的进程;    核心点: 进程池管理的进程都会使用系统的一个端口对外提供服务; ......
  • Java怎么统计每个项目下的每个类别的数据
    为了演示如何在Java中统计每个项目下的每个类别的数据,我们可以考虑一个简单的场景:假设我们有一个电商系统,需要统计每个商品分类在每个店铺下的销售数量。这里我们将使用Java的集合框架,如HashMap和ArrayList,来存储和统计数据。1.使用Java的集合框架HashMap和ArrayList来存储和统计......
  • 关于Java内存区域的理解和记录
    近期做项目遇到了FullGC的问题,干脆总结一下Java内存区域分布和垃圾回收是咋回事。Java内存区域按照线程隔离状态直接分成三大块空间:线程私有:程序计数器是一块较小的内存空间,可以看做当前线程所执行的字节码的行号指示器。在虚拟机概念模型里,字节码解释器工作时就是通过改变这......