zoukankan      html  css  js  c++  java
  • spring boot:使用async异步线程池发送注册邮件(spring boot 2.3.1)

    一,为什么要使用async异步线程池?

    1,在生产环境中,有一些需要延时处理的业务场景:

    例如:发送电子邮件,

    给手机发短信验证码

    大数据量的查询统计

    远程抓取数据等

    这些场景占用时间较长,而用户又没有必须立刻得到返回数据的需求,

    我们如果让用户占用到服务器的连接长时间等待也没有必要,

    这时异步处理是优先选择。

    2,使用线程池的好处?

         第一,提高资源利用率:可以重复利用已经创建了的线程

         第二,提高响应速度:如果有线程处于等待分配任务状态时,则任务到来时无需创建线程就能被执行

         第三,具有可管理性:线程池能减少创建和销毁线程带来的系统开销

    说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

             对应的源码可以访问这里获取: 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,是邮箱的授权码,不是登录密码,

    有疑问可以参见这一篇:

    https://www.cnblogs.com/architectforest/p/12924395.html

    四,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)
  • 相关阅读:
    栈和队列
    链表
    map
    二叉平衡树旋转
    二叉排序树详情
    红黑树详情
    查并集
    动态规划
    位操作
    字典树
  • 原文地址:https://www.cnblogs.com/architectforest/p/13386538.html
Copyright © 2011-2022 走看看