最近线上经常出现定时任务长时间运行不结束的问题,最终通过日志及线程堆栈分析定位到具体代码从而得到解决。

业务流程

我们有一个定时任务,定期读取数据库中邮箱身份验证信息(暂且称之为EmailAuthentication),并通过JavaMail使用EmailAuthentication来读取邮件以进行后面的一些业务流程。

这是一张图片

现象

最近发现部分EmailAuthentication会长时间处于运行中状态。因为除了这个现象外,还偶尔会有内存溢出的问题,一开始以为这是由内存溢出导致虚拟机崩溃导致应用程序没有将EmailAuthentication设置为未运行而造成。后来给JVM加上-XX:+HeapDumpOnOutOfMemoryError后,没有出现过内存溢出,但是问题依旧存在。

问题定位

在现有日志中未找到问题,我尝试从代码中找。之后发现尽管定时任务是多线程,但是代码配合乐观锁事务,在虚拟机不崩溃的情况下EmailAuthentication最终都会是设置成未运行

那么就只有一种可能了,应用程序还没有跑到最后一步,在某个地方卡住了。

为了验证这种可能性以及定位问题,我在定时任务的第一步最后一步分别加上了log

1
logger.info(Thread.currentThread().getName() + ":<EmailAuthentication唯一标识>" + "设置为【运行中】");
1
logger.info(Thread.currentThread().getName() + ":<EmailAuthentication唯一标识>" + "设置为【未运行】");

果然,出现问题的EmailAuthentication,只打印了第一条日志。

这是一张图片

线程名SimpleAsyncTaskExecutor-8467(不要在意线程的名字,我也知道线程池不应该用SimpleAsyncTaskExecutor)

发现问题的第一时间我就使用jpsjstack,先把堆栈信息弄到手。后来发现,我可以直接用arthas,太香了。

然后直接进行一个

1
thread -all

找到SimpleAsyncTaskExecutor-8467

这是一张图片

然后再进行一个

1
thread 32970

这是一张图片

好!问题找到了,就是这个MailUtil.fetchMail

解决

用脚想想,那应该是MailUtil.fetchMail没有设置超时时间了。

倒一倒JavaMail的源码。可以在SocketFetcher.getSocket中看到。

1
2
3
4
int cto = PropUtil.getIntProperty(props,
					prefix + ".connectiontimeout", -1);
...
int to = PropUtil.getIntProperty(props, prefix + ".timeout", -1);

默认值为-1(无限超时)

所以,设置一下这两个值就好了。我们用的pop3,所以给Properties设置的属性为

1
2
mail.pop3.connectiontimeout
mail.pop3.timeout

单位是毫秒。