从.Net到Java学习系列目录

场景描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询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是差不多的。

从.Net到Java学习第七篇——SpringBoot Redis 缓存穿透的更多相关文章

  1. 从.Net到Java学习第十一篇——SpringBoot登录实现

    从.Net到Java学习系列目录 通过前面10篇文章的学习,相信我们对SpringBoot已经有了一些了解,那么如何来验证我们的学习成果呢?当然是通过做项目来证明啦!所以从这一篇开始我将会对之前自己做 ...

  2. 从.Net到Java学习第八篇——SpringBoot实现session共享和国际化

    从.Net到Java学习系列目录 SpringBoot Session共享 修改pom.xml添加依赖 <!--spring session--> <dependency> & ...

  3. Java学习第七篇:与运行环境交互

    目录 一.与用户互动 1.main方法形参 2.使用Scanner类获取输入 3.使用BufferedReader类获取输入 二.常用类 1.System类和Runtime类 2.String, St ...

  4. 从.Net到Java学习第六篇——SpringBoot+mongodb&Thymeleaf&模型验证

    SpringBoot系列目录 SpringBoot整合mongodb MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.如果你没用过Mong ...

  5. Java学习之反射篇

    Java学习之反射篇 0x00 前言 今天简单来记录一下,反射与注解的一些东西,反射这个机制对于后面的java反序列化漏洞研究和代码审计也是比较重要. 0x01 反射机制概述 Java反射是Java非 ...

  6. Java学习之jackson篇

    Java学习之jackson篇 0x00 前言 本篇内容比较简单,简单记录. 0x01 Json 概述 概述:JSON(JavaScript Object Notation, JS 对象简谱) 是一种 ...

  7. Java学习之注解篇

    Java学习之注解篇 0x00 前言 续上篇文章,这篇文章就来写一下注解的相关内容. 0x01 注解概述 Java注解(Annotation)又称Java标注,是JDK5.0约会的一种注释机制. 和J ...

  8. 第04项目:淘淘商城(SpringMVC+Spring+Mybatis)【第七天】(redis缓存)

    https://pan.baidu.com/s/1bptYGAb#list/path=%2F&parentPath=%2Fsharelink389619878-229862621083040 ...

  9. 从.Net到Java学习第四篇——spring boot+redis

    从.Net到Java学习系列目录 “学习java已经十天,有时也怀念当初.net的经典,让这语言将你我相连,怀念你......”接上一篇,本篇使用到的框架redis.FastJSON. 环境准备 安装 ...

随机推荐

  1. JS日历插件 - My97 DatePicker用法详解

    一.简介 1. 注意事项 (1)My97DatePicker目录是一个整体,不可破坏里面的目录结构,也不可对里面的文件改名,可以改目录名: (2)各目录及文件的用途: WdatePicker.js 配 ...

  2. 利用Python实现对Web服务器的目录探测

    今天是一篇提升技能的干货分享,操作性较强,适用于中级水平的小伙伴,文章阅读用时约3分钟. PART 1/Python Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python ...

  3. FFmpeg 结构体学习(三): AVPacket 分析

    在上文FFmpeg 结构体学习(二): AVStream 分析我们学习了AVStream结构体的相关内容.本文,我们将讲述一下AVPacket. AVPacket是存储压缩编码数据相关信息的结构体.下 ...

  4. [Swift]LeetCode778. 水位上升的泳池中游泳 | Swim in Rising Water

    On an N x N grid, each square grid[i][j]represents the elevation at that point (i,j). Now rain start ...

  5. [Swift]LeetCode808. 分汤 | Soup Servings

    There are two types of soup: type A and type B. Initially we have N ml of each type of soup. There a ...

  6. [Swift]LeetCode965. 单值二叉树 | Univalued Binary Tree

    A binary tree is univalued if every node in the tree has the same value. Return true if and only if ...

  7. Linux 工程向 Windows 平台迁移的一些小小 tips

    Linux 工程向 Windows 平台迁移的一些小小 tips VS2013 C++11 Visual Studio 2013 没有做到对 C++11 所有的支持,其中存在的一个特性就是 In-cl ...

  8. 关于ML.NET v0.8的发布说明

    ML.NET允许您创建和使用针对场景的机器学习模型,以实现常见任务,如情绪分析,问题分类,预测,推荐,欺诈检测,图像分类等.您可以使用ML.NET示例在GitHub仓库中查看这些常见任务 .ML.NE ...

  9. Quartz.NET学习笔记(一) 简介

    Quartz.NET是一款开源的任务调度框架,他是由Java中的任务调度框架Quartz移植而来的.官方网站https://www.quartz-scheduler.net/. Quartz.Net的 ...

  10. Android--调用系统照相机拍照与摄像

    前言 在很多场景中,都需要用到摄像头去拍摄照片或视频,在照片或视频的基础之上进行处理.但是Android系统源码是开源的,很多设备厂商均可使用,并且定制比较混乱.一般而言,在需要用到摄像头拍照或摄像的 ...