Spring中使用Async进行异步功能开发实战-以大文件上传为例
目录
前言
一、场景再现
1、Event的同步机制
二、性能优化
1、异步支持配置
2、自定义处理线程池扩展
3、将线程池配置类绑定到异步方法
三、总结
前言
在之前的博客中,曾将讲了在SpringBoot中如何使用Event来进行大文件上传的解耦,原文地址:使用SpringEvent解决WebUploader大文件上传解耦问题,在这篇博客当中,我们使用Event机制成功的将大文件的上传和解析的功能进行分离,已经实现了解耦的需求。但是在真实项目中会存在一个问题,就是解耦是解耦了。但是我们期望程序能够做到异步,也就是将文件的上传和解析进行彻底的异步化。后台程序在接收前端请求的文件时,文件上传完成后就结束。而对于上传文件的处理和解析等操作则放到解析程序中。整个过程给人的感觉就是到上传就完成了,解析则可以在后台慢慢运行,等待执行完成即可。
这里我们仍然以大文件上传为例,首先讲解在未进行程序异步化的时候,程序的运行机制和表现。然后讲解如何进行异步化的改造,让程序进行异步执行。通过本文不仅能让你掌握如何进行Event的事件开发,同时还能掌握在Spring中如何进行异步开发,熟悉@Async的具体用法。
一、场景再现
为了能让大家对故事的场景有更加直观的认识,这里我们将场景进行再现,让大家看到具体的问题。带着问题,我们一起来寻找解决办法,这样对前因后果更加清楚。
1、Event的同步机制
首先我们来看一下原来的事件分离处理代码,关键代码如下:
@EventListener public void fileUploadEventRegister(FileUploadEvent event){ try { sys_user_logger.info("当前处理线程名称:" + Thread.currentThread().getName()); FileEntity fileEntity = event.getFileEntity(); if(StringUtils.isNotEmpty(fileEntity.getTablename())){ FileUploadServiceRegisterEnum rigisterEnum = null; if(StringUtils.isNotBlank(fileEntity.getBizType())) {//业务类型不为空,则根据表名和业务名称来查找执行service rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableNameAndBizType(fileEntity.getTablename(), fileEntity.getBizType()); }else { rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableName(fileEntity.getTablename()); } if(null != rigisterEnum && StringUtils.isNotEmpty(rigisterEnum.getExecService())){ String execService = rigisterEnum.getExecService(); IFileUploadCallbackService service = SpringUtils.getBean(execService); service.process(fileEntity); }else{ sys_user_logger.info("未注册文件上传监听回调处理器."); } } } catch (Exception e) { sys_user_logger.error("文件上传事件监听发生错误.",e); } }
在这里为了让文件处理的时间加长,我们可以把文件处理的逻辑加上一个线程的等待时间比如休眠等待35秒钟。代码如下:
@Override @Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class) public void process(FileEntity fileEntity) throws Exception { if(null != fileEntity && StringUtils.isNotEmpty(fileEntity.getBid())){ String pkId = fileEntity.getBid(); Student stu = studentService.selectStudentById(Long.valueOf(pkId)); //System.out.println(fileEntity.getPath()); //System.out.println(stu.getName() + "\t" + stu.getAddress()); logger.info("开始处理........"); Thread.sleep(35 * 1000);//休眠35秒测试 logger.info("执行结束"); } }
在这里,我们使用Thread.sheep这个方法来让线程进行休眠等待。模拟文件上传后,解析很慢的场景。由于卡顿变慢,下面的界面也一直处于等待的状态。
下面是原来的处理线程信息,通过日志可以看到,相关的执行线程都是[http-nio-8080-exec-26]:
20:27:11.280 [http-nio-8080-exec-26] INFO sys-user - [fileUploadEventRegister,32] - 当前处理线程名称:http-nio-8080-exec-26 20:27:11.281 [http-nio-8080-exec-26] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - ==> Preparing: SELECT id,name,sex,birthday,address,remark,create_by,create_time,update_by,update_time FROM biz_student WHERE id=? 20:27:11.284 [http-nio-8080-exec-26] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - ==> Parameters: 1811048705298571266(Long) 20:27:11.287 [http-nio-8080-exec-26] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - Parameters: 1811048705298571266(Long) 20:52:48.971 [Async-FileUpload-Thread-1] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - Preparing: SELECT count(0) FROM biz_file WHERE (f_state = ? AND b_id = ? AND table_name = ? AND biz_type = ?) 20:52:48.984 [http-nio-8080-exec-71] DEBUG c.y.p.w.m.F.selectList_COUNT - [debug,137] - ==> Parameters: 1(Integer), 1811048705298571266(String), biz_student(String), 123a(String) 20:52:49.003 [http-nio-8080-exec-71] DEBUG c.y.p.w.m.F.selectList_COUNT - [debug,137] - Preparing: SELECT id, f_id, b_id, f_type AS type, f_name AS name, f_desc AS desc, f_state AS state, f_size AS size, f_path AS path, table_name, md5code, directory, biz_type, create_by, create_time, update_by, update_time FROM biz_file WHERE (f_state = ? AND b_id = ? AND table_name = ? AND biz_type = ?) order by create_time desc LIMIT ? 20:53:23.973 [Async-FileUpload-Thread-1] INFO sys-user - [process,34] - 执行结束
可以很明显的看到,文件的解析程序是使用Async-FileUpload-Thread开头的线程来进行处理的,即表名是正常的使用线程池来进行处理相关的业务。也说明的我们的设计达到了预期,即实现了程序的完全异步化。
三、总结
以上就是本文的主要内容,本文以大文件上传为例,首先讲解在未进行程序异步化的时候,程序的运行机制和具体表现。然后讲解如何进行异步化的改造,让程序进行异步执行。通过本文不仅能让你掌握如何进行Event的事件开发,同时还能掌握在Spring中如何进行异步开发,熟悉@Async的具体用法。行文仓促,难免有不足之处,如果有表达不当的地方或者不足之处,还请各位专家批评指正,在评论区留下您的真知灼见,万分荣幸。