spring boot:使用async异步线程池发送注册邮件(spring boot 2.3.1)
一,为什么要使用async异步线程池?
1,在生产环境中,有一些需要延时处理的业务场景:
例如:发送电子邮件,
给手机发短信验证码
大数据量的查询统计
远程抓取数据等
这些场景占用时间较长,而用户又没有必须立刻得到返回数据的需求,
我们如果让用户占用到服务器的连接长时间等待也没有必要,
这时异步处理是优先选择。
2,使用线程池的好处?
第一,提高资源利用率:可以重复利用已经创建了的线程
第二,提高响应速度:如果有线程处于等待分配任务状态时,则任务到来时无需创建线程就能被执行
第三,具有可管理性:线程池能减少创建和销毁线程带来的系统开销
说明:刘宏缔的架构森林是一个专注架构的博客,
网站:https://blog.imgtouch.com
本文: https://blog.imgtouch.com/index.php/2023/05/24/springboot-shi-yong-async-yi-bu-xian-cheng-chi-fa-song-zhu-ce-you-jian-springboot231/
对应的源码可以访问这里获取: https://github.com/liuhongdi/
说明:作者:刘宏缔 邮箱: 371125307@qq.com
二,演示项目的相关信息
1,项目地址:
https://github.com/liuhongdi/asyncmail
2,项目的说明:
regmail:演示异步发送一封注册成功的邮件
sleep:同步执行sleep1秒
asyncsleep:演示异步执行十个各sleep1秒的线程
3,项目的结构,如图:
三,演示项目的配置文件说明:
1,pom.xml
<!--mail begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <!--mail end--> <!--thymeleaf begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--thymeleaf end-->
引入mail和thymeleaf两个依赖,其中thymeleaf是作为html格式邮件内容的模板
2,application.properties
spring.mail.host=smtp.163.com spring.mail.username=demouser@163.com spring.mail.password=demopassword spring.mail.default-encoding=UTF-8 spring.mail.protocol=smtps spring.mail.port=465 spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true
说明:此处注意:如果是在阿里云ecs上运行,
不要使用25的smtp端口,要使用带ssl的smtps,端口是465
另外:这里配置的password,是邮箱的授权码,不是登录密码,
有疑问可以参见这一篇:
四,java代码说明:
1,AsyncConfig.java
//线程池的配置 @Configuration @EnableAsync public class AsyncConfig { @Bean(name = "taskExecutor") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置核心线程数,它是可以同时被执行的线程数量 executor.setCorePoolSize(5); // 设置最大线程数,缓冲队列满了之后会申请超过核心线程数的线程 executor.setMaxPoolSize(10); // 设置缓冲队列容量, executor.setQueueCapacity(20); // 设置线程生存时间(秒),当超过了核心线程出之外的线程在生存时间到达之后会被销毁 executor.setKeepAliveSeconds(60); // 设置线程名称前缀 executor.setThreadNamePrefix("threadpool-"); // 设置拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); //初始化 executor.initialize(); return executor; } }
说明:线程池的配置文件,
这个有个问题:核心线程数设置为多少为宜?
设N为cpu的核心数量,则
如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
这个是常用的一个设置参考,具体是否适用自己的业务还要在生产环境中观察
但可以确认的是:线程数不是越多越好,因为所在机器上的cpu等硬件并没有变化,
异步也只是提高吞吐量,并不能加快任务的执行
2,MailUtil.java
@Component public class MailUtil { @Resource private JavaMailSender javaMailSender; @Resource TemplateEngine templateEngine; //发送html内容的邮件,使用thymeleaf渲染页面 public void sendHtmlMail(String from, String[] to, String[] cc, String[] bcc, String subject, String templateName, HashMap<String,String> content) throws MessagingException { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject(subject); helper.setFrom(from); helper.setTo(to); //抄送,收到邮件用户可以看到其他收件人 if (cc != null && cc.length > 0) { helper.setCc(cc); } //密送 收到邮件用户看不到其他收件人 if (bcc != null && bcc.length > 0) { helper.setBcc(bcc); } helper.setSentDate(new Date()); //生成邮件模板上的内容 Context context = new Context(); if (content != null && content.size() > 0) { for (String key : content.keySet()) { context.setVariable(key, content.get(key)); } } String process = templateEngine.process(templateName, context); helper.setText(process,true); javaMailSender.send(mimeMessage); } }
说明:功能是发送html内容的邮件,所以用到了templateEngine
3,邮件内容:regmail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>您好,注册成功! 以下是您在本网站的注册信息:</h1> <table border="1"> <tr> <td>用户名</td> <td th:text="${username}">${username}</td> </tr> <tr> <td>昵称</td> <td th:text="${nickname}">${nickname}</td> </tr> <tr> <td>ID</td> <td th:text="${id}">${id}</td> </tr> </table> <div style="color: #ff1a0e">本站网址:http://springio.com</div> </body> </html>
4,MailServiceImpl.java
@Service public class MailServiceImpl implements MailService { @Resource private MailUtil mailUtil; //异步发送html格式的邮件 @Async @Override public void sendHtmlMail() { String from = "demouser@163.com"; String[] to = {"371125307@qq.com"}; String subject = "恭喜您成功注册老刘代码库网站"; HashMap<String,String> content= new HashMap<String,String>(); content.put("username","laoliu"); content.put("nickname","老刘"); content.put("id","0000001"); String templateName= "mail/regmail.html"; try { mailUtil.sendHtmlMail(from, to, null, null, subject, templateName, content); } catch (MessagingException e) { e.printStackTrace(); System.out.println("邮件发送出错"); } } }
功能:生成邮件的内容,注意这里指定的模板文件和模板上的变量
5,SlowServiceImpl.java
@Service public class SlowServiceImpl implements SlowService { private static Logger log= LoggerFactory.getLogger(SlowServiceImpl.class); //sleep 1秒 @Override public void sleepawhile(){ long startTime = System.currentTimeMillis(); log.info("function sleep begin"); try { Thread.sleep(1000); //延时1秒 } catch(InterruptedException e) { e.printStackTrace(); } log.info("function sleep end"); } //sleep 1秒,异步执行,并返回一个统计用的字串 @Async @Override public Future<String> asyncsleepawhile(int i){ log.info("async function sleep begin"); String start=TimeUtil.getMilliTimeNow(); try { Thread.sleep(1000); //延时1秒 } catch(InterruptedException e) { e.printStackTrace(); } log.info("async function sleep end"); String end=TimeUtil.getMilliTimeNow(); return new AsyncResult<>(String.format("第{%s}个异步调用asyncsleepawhile方法:开始时间:%s,结束时间:%s", i,start,end)); } }
仅供演示用,两个方法:一个同步sleep,一个异步sleep
6,HomeController.java
@RequestMapping("/home") @Controller public class HomeController { private static Logger log= LoggerFactory.getLogger(HomeController.class); @Resource private MailService mailService; @Resource private SlowService slowService; //异步发送一封注册成功的邮件 @GetMapping("/regmail") @ResponseBody public String regMail(ModelMap modelMap) { mailService.sendHtmlMail(); return "mail sended"; } //同步sleep1秒 @GetMapping("/sleep") @ResponseBody public String sleep() { System.out.println(TimeUtil.getMilliTimeNow()+" controller begin"); slowService.sleepawhile(); System.out.println(TimeUtil.getMilliTimeNow()+" controller end"); return "mail sended"; } //异步执行sleep1秒10次 @GetMapping("/asyncsleep") @ResponseBody public Map<String, Object> asyncsleep() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); Map<String, Object> map = new HashMap<>(); List<Future<String>> futures = new ArrayList<>(); for (int i = 0; i < 10; i++) { Future<String> future = slowService.asyncsleepawhile(i); futures.add(future); } List<String> response = new ArrayList<>(); for (Future future : futures) { String string = (String) future.get(); response.add(string); } map.put("data", response); map.put("消耗时间", String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - start)); return map; } }
五,效果测试:
1,发送注册邮件:
访问:
http://127.0.0.1:8080/home/regmail
收到邮件:
2,测试异步线程池:
访问:
http://127.0.0.1:8080/home/asyncsleep
查看输出:
{"data":["第{0}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.937,结束时间:2020-07-27 17:10:11.941",
"第{1}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.937,结束时间:2020-07-27 17:10:11.945",
"第{2}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.939,结束时间:2020-07-27 17:10:11.940",
"第{3}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.938,结束时间:2020-07-27 17:10:11.940",
"第{4}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.939,结束时间:2020-07-27 17:10:11.941",
"第{5}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.943,结束时间:2020-07-27 17:10:12.944",
"第{6}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.944,结束时间:2020-07-27 17:10:12.944",
"第{7}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.944,结束时间:2020-07-27 17:10:12.945",
"第{8}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.945,结束时间:2020-07-27 17:10:12.946",
"第{9}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.946,结束时间:2020-07-27 17:10:12.946"],
"消耗时间":"任务执行成功,耗时{2023}毫秒"}
这里大家注意思考一下,每个线程sleep了一秒,
我们启用了10个线程,为什么用的时间是2秒?
原因在于我们对线程池中核心线程数量的设置:可以并发执行5个线程,
所以10个线程共执行了两次,每次5个,每次都是1秒,两次用时2秒
六,查看spring boot版本:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE)