下面的这个方法是发邮件的抽象出来的一个公用方法:
String[] to 表示收件人列表;
subject 邮件主题;
templateName 邮件末班,用velocity写的,
Map params 参数,用来填充velocity中的某些字段取值的
public void sendHtmlWithTemplate(String[] to, String subject, String templateName, Map<String, Object> params) {
final MimeMessage mimeMessage = mailSender.createMimeMessage();
try {
final MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage);
messageHelper.setFrom(simpleMailMessage.getFrom());
if (ENV_ONLINE.equals(environment)) {
messageHelper.setTo(to);
messageHelper.setSubject(subject);
} else {
messageHelper.setTo(adminEmail);
messageHelper.setSubject(subject + Arrays.asList(to));
}
messageHelper.setSentDate(new Date());
final String content = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, templateName, "UTF-8",
params);
final String[] logTo = to;
messageHelper.setText(content, true);
new Thread() {
@Override
public void run() {
mailSender.send(mimeMessage);
logger.error("Mailsentto: " + Arrays.asList(logTo) + "\nContent: " + content);
}
}.start();
} catch (Exception e) {
logger.error("emailServiceError error:" + e.getMessage(), e);
}
}
在上面的发邮件的代码中,使用了内部类如下:
new Thread() {
@Override
public void run() {
mailSender.send(mimeMessage);
logger.error("Mailsentto: " + Arrays.asList(logTo) + "\nContent: " + content);
}
}.start();
我觉得在这个地方做这个控制是很恰当的吧,为什么team leader让我删掉这个new Thread()的部分。
原话:“把new Thread全部删掉,这些邮件发送不了的bug都没有暴露出来; 邮件发送模块的代码采用异步发送方式,失去异常事务回滚的能力,new Thread要全部删除;” 不懂是什么意思啊?
问题:
1.为什么要去掉new Thread()?
2.在什么样的并发业务场景下,需要使用new Thread()这样的方式?有没有更好的解决方法?
3.在并发场景下,使用new Thread().start()的方式有什么弊端?每次new Thread新建对象性能会很差么?邮件服务也是当触发某个业务规则的时候,可能需要大量发送一下邮件,用线程池好不好呢?
伊谢尔伦2017-04-18 09:21:21
@有明 已經說了下匿名內部類比較蛋疼的地方。我再結合你的業務來說一下:
根據現有的程式碼邏輯,我們郵件服務對外暴露的應該是sendHtmlWithTemplate
这个方法,而不是 mailSender.send
,所以在我们对外暴露的sendHtmlWithTemplate
這個方法裡面就不需要再重啟一個線程異步去發送郵件,因為對於調用方,其實你需要告訴它這次發送郵件是否成功,如果失敗了/異常了,失敗的原因是什麼。如果按照你現有的方案的話,可能給外面感覺就是,不管郵件發送成功或者失敗,外面都是沒有感知的,意味著不能將這封郵件持久化,並且過一段時間再重試。所以非同步發送郵件應該交給你的呼叫方,而不是在你的方法裡面
假設我們的發送郵件可能是一個很耗時的動作,那麼按照每來一封郵件就啟一個線程去做的話,意味這到時候系統裡面會產生很多線程。那麼就意味會有很多線程切換,線程的切換,線程的切換就會有阻塞和喚醒,這些都涉及到用戶態和內核態的轉換或者是線程的上下文的切換,都是很耗CPU的,極端情況如果系統線程不段堆積的話,會導致整個服務DOWN機,服務處於不可用的狀態。 new Thread()確實是個很昂貴的操作
如果正確的做非同步發送郵件,確實需要放到線程池裡面做
更加的好的方法,一種就是透過MQ來發送郵件訊息(考慮訊息常見蛋疼的問題1.訊息是否會遺失2.訊息重發了怎麼辦),單獨交給一台發送郵件的伺服器去做。還有一種就是先存入資料庫表,如果時效性要求不是很高的話,到時候啟一個定時器慢慢發。
高洛峰2017-04-18 09:21:21
匿名物件很方便,但匿名物件也是魔鬼,使用不好就能讓你的應用程式崩塌。就拿你這裡做例子,如果你沒有控制好發送郵件的參數,比如發送時超時了就繼續等待,那這個線程可就變成了孤魂野鬼了,找也找不到,關也關不掉,這是程式設計師該做的事情嗎?
你問為何你的頭讓你把這裡去掉,我只能說是他還不夠相信的你能力,所以不敢讓你操縱這個魔鬼。
你問什麼場景下適合,我只能說沒有適合的場景,只有有能力的人。
天蓬老师2017-04-18 09:21:21
不好意思,現在才看到問題。
具體參考@iMouseWu 和 @有明 的答案。
如果你有寫過js,你可以理解這裡的new Thread()
相当于ajax
請求,是屬於異步行為,在這個線程任務還沒執行完之前,方法已經返回給調用方了(而且不會拋出發送郵件產生的異常) ,這也就是你leader說的「這些郵件發送不了的bug都沒有暴露出來」。
考慮並發量大的話可以採用redis
+kafka
(或其他訊息元件),用訊息的形式去通知郵件服務發送郵件。