Redis 缓存穿透

Redis 缓存穿透

https://www.cnblogs.com/jiekzou/p/9212114.html

场景描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。

穿透:频繁查询一个不存在的数据,由于缓存不命中,每次都要查询持久层。从而失去缓存的意义。

常用解决办法:
①用一个bitmap和n个hash函数做布隆过滤器过滤没有缓存的键。
②持久层查询不到就缓存空结果,有效时间为数分钟。

我这里使用的是双重检测同步锁方式。

修改AreaService接口,添加如下两个接口方法,selectAllArea2方法是可能会导致缓存穿透的方法。

List<Area> selectAllArea();
List<Area> selectAllArea2();

修改接口的实现类AreaServiceImpl:

复制代码
@Autowired
private RedisService redisService;
private JSONObject json = new JSONObject();

/**
 * 从缓存中获取区域列表
 *
 * @return
 */
private List<Area> getAreaList() {
    String result = redisService.get("redis_obj_area");
    if (result == null || result.equals("")) {
        return null;
    } else {
        return json.parseArray(result, Area.class);
    }
}

@Override
public List<Area> selectAllArea() {
    List<Area> list = getAreaList();
    if (list == null) {
        synchronized (this) {
            list = getAreaList(); //双重检测锁
            if (list == null) {
                list = areaMapper.selectAllArea();
                redisService.set("redis_obj_area", json.toJSONString(list));
                System.out.println("请求的数据库。。。。。。");
            } else {
                System.out.println("请求的缓存。。。。。。");
            }
        }
    } else {
        System.out.println("请求的缓存。。。。。。");
    }
    return list;
}

@Override
public List<Area> selectAllArea2() {
    List<Area> list = getAreaList();
    if (list == null) {
        list = areaMapper.selectAllArea();
        redisService.set("redis_obj_area", json.toJSONString(list));
        System.out.println("请求的数据库。。。。。。");
    } else {
        System.out.println("请求的缓存。。。。。。");
    }
    return list;
}

复制代码
运行程序,在浏览器中输入地址http://localhost:8083/boot/getAll,第一次访问

2018-06-22 10:21:24.730 INFO 10436 --- [nio-8083-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
请求的数据库。。。。。。
刷新浏览器地址,第二次访问

请求的缓存。。。。。。
再打开我们的redis可视化管理工具

在之前配置mysql数据库连接的时候,由于没有指定是否采用SSL,所以控制台会有一个警告信息,如下所示:

这个是因为使用的mysql版本比较高,要求开启SSL,所以控制台会有一个警告,当然,你也可以忽略,如果要去除这个警告,可以在之前的mysql连接配置后面添加:&useSSL=false

datasource:
url: jdbc:mysql://localhost:3306/demo?&useSSL=false
删除redis中的这个key值,我们通过使用一个并发测试工具来模拟缓存穿透的现象,这里使用到了jmeter这个并发测试工具。jmeter官网: https://jmeter.apache.org/。jmeter更多使用教程:https://www.yiibai.com/jmeter/

将jmter下载到本地,然后解压,双击jmeter.bat运行

(1)右键单击“测试计划”,新建测试组

(2)新建HTTP请求

(3)保存并运行测试,这是时候其实已经在开始运行了,我们可以通过“选项"——“Log Viewer",来查看运行日志。

此时再查看IDEA中的控制台运行情况如下:

我们看到有四次进行了数据库查询,而我们想要的其实是只进行一次数据库查询,其它的都是直接从缓存中进行查询。

重新删除redis中的key值redis_obj_area,我们再来测试一下采用了双重检测同步锁的方法selectAllArea2

修改jmeter中的请求路径

然后运行,我们再看下IDEA中控制台中的记录:

现在只有第一次是从数据库中读取了。

当然,如果我们不采用测试工具的话,我们也可以自己写一个单元测试,来进行并发测试。

单元测试类AreaServiceImplTest的代码:

复制代码
@RunWith(SpringRunner.class)
@SpringBootTest
public class AreaServiceImplTest {
@Autowired
public AreaService areaService;
@Before
public void setUp() throws Exception {

}

@Test
public void selectAllArea() throws InterruptedException {
    final CountDownLatch latch= new CountDownLatch(4);//使用java并发库concurrent
    //启用10个线程
    for(int i=1;i<=10;i++){
        new Thread(new Runnable(){
            public void run(){
                try {
                    //Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                areaService.selectAllArea();
                System.out.println(String.format("子线程%s执行!",Thread.currentThread().getName()));
                latch.countDown();//让latch中的数值减一
            }
        }).start();
    }
    //主线程
    latch.await();//阻塞当前线程直到latch中数值为零才执行
    System.out.println("主线程执行!");
}
@Test
public void selectAllArea2() throws InterruptedException {
    final CountDownLatch latch= new CountDownLatch(4);//使用java并发库concurrent
    //启用10个线程
    for(int i=1;i<=10;i++){
        new Thread(new Runnable(){
            public void run(){
                try {
                    //Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                areaService.selectAllArea2();
                System.out.println(String.format("子线程%s执行!",Thread.currentThread().getName()));
                latch.countDown();//让latch中的数值减一
            }
        }).start();
    }
    //主线程
    latch.await();//阻塞当前线程直到latch中数值为零才执行
    System.out.println("主线程执行!");
}
@Test
public void selectAllArea3(){
    Runnable runnable=new Runnable() {
        @Override
        public void run() {
            areaService.selectAllArea2();
        }
    };
    ExecutorService executorService=Executors.newFixedThreadPool(4);
    for (int i=0;i<10;i++){
        executorService.submit(runnable);
    }
}

}
复制代码
运行结果,和使用jmeter是差不多的。

原文地址:https://www.cnblogs.com/Leo_wl/p/9251637.html

时间: 2024-08-27 23:37:48

Redis 缓存穿透的相关文章

Redis缓存穿透和缓存雪崩以及解决方案

Redis缓存穿透和缓存雪崩以及解决方案 Redis缓存穿透和缓存雪崩以及解决方案缓存穿透解决方案布隆过滤缓存空对象比较缓存雪崩解决方案保证缓存层服务高可用性依赖隔离组件为后端限流并降级数据预热缓存并发分布式锁 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透: 解决方案 布隆过滤 对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系

redis缓存穿透,缓存击穿,缓存雪崩原因+解决方案

###一.前言在我们日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题. 为了

Redis缓存穿透和雪崩

一.缓存雪崩 1. 缓存挂了,所有请求都到了数据库了 2. 缓存没有挂,但同时到期,正好把所有缓存都删除了,所有请求都到了数据库了 3. 所有请求都到了数据库,很可能把数据库搞挂 二.缓存雪崩的解决方法 1. 缓存挂了的情况 a. 事发前:实现redis的高可用性(主从+sentinal+cluster) b. 事发时:本地缓存+限流(hystrix) c. 事发后:Redis持久化,重启后从磁盘上加载数据,快速恢复 三.缓存穿透 1. 查询一个不存在的数据,由于没有从数据库里查到,就不放入缓存

Redis缓存穿透、缓存雪崩

缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞. 解决方案 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查

redis缓存穿透、缓存击穿、缓存雪崩

缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透. 解决办法: 预校验 在控制层对查询参数先进行校验,不符合则丢弃. 布隆过滤 将所有可能查询的参数添加到BloomFilter中,一定不存在的记录就会被BloomFilter过滤掉,从而避免了对底层存储系统的查询压力. 缓存空对象 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但

Redis缓存穿透、缓存雪崩、缓存击穿

缓存穿透: ? 缓存穿透,是指查询一个数据库一定不存在的数据.正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存.如果数据库查询对象为空,则不放进缓存. 代码流程 参数传入对象主键ID 根据key从缓存中获取对象 如果对象不为空,直接返回 如果对象为空,进行数据库查询 如果从数据库查询出的对象不为空,则放入缓存(设定过期时间) ? 想象一下这个情况,如果传入的参数为-1,会是怎么样?这个-1,就是一定不存在的对象.

redis缓存穿透-解决方案

上面的解决方案个人觉得时有误的,因为就算缓存了value的null值,后面的接口请求还是会判断走数据库,所以看解决方案二 解决方案二: https://blog.csdn.net/muyi_amen/article/details/80229647   参考了博客 对于数据库中不存在的key,数据库查询的值为空,也将key对应的value缓存到redis上. 查询数据库前,到redis上判断key是否存在,如果存在,直接返回value,不管是否为空,这样就不会再去查数据库,达到了缓解数据库的作用

redis缓存介绍以及常见问题浅析

# 没缓存的日子: 对于web来说,是用户量和访问量支持项目技术的更迭和前进.随着服务用户提升.可能会出现一下的一些状况: 页面并发量和访问量并不多,mysql足以支撑自己逻辑业务的发展.那么其实可以不加缓存.最多对静态页面进行缓存即可. 页面的并发量显著增多,数据库有些压力,并且有些数据更新频率较低反复被查询或者查询速度较慢.那么就可以考虑使用缓存技术优化.对高命中的对象存到key-value形式的redis中,那么,如果数据被命中,那么可以省经效率很低的db.从高效的redis中查找到数据.

Redis系列十:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

一.缓存雪崩 缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机.从而形成一系列连锁反应,造成整个系统崩溃. 缓存正常从Redis中获取,示意图如下: 缓存失效瞬间示意图如下: 缓存雪崩的解决方案: (1)碰到这种情况,一般并发量不是特别多的时候,使用最多的解决方案是加锁排队,伪代码如下: 加锁排队只是为了