博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从.Net到Java学习第七篇——SpringBoot Redis 缓存穿透
阅读量:6138 次
发布时间:2019-06-21

本文共 4865 字,大约阅读时间需要 16 分钟。

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

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

常用解决办法:

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

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

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

List selectAllArea();    List selectAllArea2();

修改接口的实现类AreaServiceImpl

@Autowired    private RedisService redisService;    private JSONObject json = new JSONObject();    /**     * 从缓存中获取区域列表     *     * @return     */    private List 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 selectAllArea() {        List 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 selectAllArea2() {        List 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官网:  。jmeter更多使用教程:

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

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

(2)新建HTTP请求

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

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

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

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

修改jmeter中的请求路径

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

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

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

 单元测试类AreaServiceImplTest的代码:

@RunWith(SpringRunner.class)@SpringBootTestpublic 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是差不多的。

转载地址:http://ibuya.baihongyu.com/

你可能感兴趣的文章
《游戏程序设计模式》 2 - 顺序模式
查看>>
数据过滤器注解@Filter 如何在hibernate、spring data jpa中调用
查看>>
Eclipse上GIT插件EGIT使用手册之九_Rebase和Merge的区别
查看>>
关闭进程中打印信息
查看>>
安装memcached软件并用简单脚本做测试
查看>>
MySQL表新增字段默认值处理的一处小细节
查看>>
MEMCACHE TIME_WAIT过多的解决方法
查看>>
CITRIX技术峰会,浦东香格里拉之旅
查看>>
队列的顺序存储结构
查看>>
桂林游览3
查看>>
eclipse创建maven_web项目
查看>>
mysql不能远程访问
查看>>
python通信模块——zmq的安装(包括ubuntu更新源)
查看>>
Spring_AOP 记录系统关键操作日志用法
查看>>
25岁前你要学会放下的八样东西
查看>>
【电路】PADS建库细节
查看>>
wordpress学习笔记
查看>>
mysql 1449 : The user specified as a definer ('root'@'%') does not exist
查看>>
c#如何将子窗体显示到父窗体的容器(panel)控件中
查看>>
linux运维之docker交付篇
查看>>