业务场景,用户登录后给用户发送一封通知邮件,邮件消息的产生和发送用ActiveMQ来实现,预想的效果是用户登录成功后直接返回登录成功的信息,邮件的发送在后台程序中启动一个线程,采用异步的方式来调用邮件发送的方法
最开始采用如下代码实现:
<pre><code>
/**
* 单个线程的线程池
*/
private static ExecutorService executorService = Executors.newSingleThreadExecutor();
</code></pre>
<pre><code>
executorService.submit(new Runnable() {
@Override
public void run() {
sendLoginMail(toMail, subject, content);
}
});
</code></pre>
但是后面发现一个问题,就是在发送邮件的过程中出现了异常,上面的代码是没办法得知的,不会有异常信息抛出,不便于问题的定位,所以就改为了以下这种方式:
<pre><code>
FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
sendLoginMail(toMail, subject, content);
return "ok";
}
});
executorService.submit(futureTask);
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
logger.error("发送邮件异常...");
} catch (ExecutionException e) {
logger.error("发送邮件异常...");
e.printStackTrace();
}
</code></pre>
任务提交后调用futureTask.get()方法获取执行的结果,这样做能实现如果发生邮件的过程中出现了异常,那么异常信息会打印出来,
但是,但是,但是在今天测试登录功能的时候提示服务调用超时,后面查找到原因是因为ActiveMQ挂掉了,导致登录后向ActiveMQ里面发送消息的时候一直等待,直到服务调用超时,抛出异常。
那么问题就来了,为什么我是用线程池做任务的异步提交的,还会等到邮件发送的代码执行完才会返回呢?
原因就是:futureTask.get()方法,它会等到任务执行完后才能获取到执行的结果,所以这相当于还是同步,如果把上面代码的futureTask.get()段注释掉,再运行则能达到异步的效果。
附:
<pre><code>
/**
* 由通知服务将邮件信息发送至消息队列
*
* @param toMail
* @param subject
* @param content
*/
private void sendLoginMail(String toMail, String subject, String content) {
MailParam mailParam = new MailParam(toMail, subject, content);
notifySendFacade.sendMailNotify(mailParam);
}
</code></pre>
<pre><code>
/**
* 发送邮件通知
*
* @param mailParam
*/
@Override
public void sendMailNotify(final MailParam mailParam) {
logger.info("开始发送邮件消息...");
notifyJmsTemplate.send(new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(NotifyUtil.formatMail(mailParam));
}
});
logger.info("邮件发送结束...");
}
</code></pre>
心得感悟:
1、代码中加入必要的日志信息对后续问题的定位分析真的很重要,特别是对于一些关键性的步骤,对于上面的问题,一开始我也一脸懵逼,直到看到日志中有输出”开始发送邮件消息“,但是却一直没有输出”邮件发送结束“,所以判断可能是ActiveMQ挂掉了,果不其然,对问题的定位能更加快速。
2、对异常的正确处理也很重要。