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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM