从.Net到Java学习第七篇——SpringBoot Redis 缓存穿透
场景描述:我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询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是差不多的。
博客地址: | http://www.cnblogs.com/jiekzou/ | |
博客版权: | 本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。 如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步! 再次感谢您耐心的读完本篇文章。 |
|
其它: |
.net-QQ群4:612347965
java-QQ群:805741535
H5-QQ群:773766020 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?