Spring调度程序突然停止

64

我们有一个运行在Tomcat 6上的Spring 3 web应用程序,使用@Scheduled注解实现了多个计划任务(主要用于每天晚上运行的作业)。然而,有时候(很少,也许每两个月一次),调度线程会停止工作,因此在随后的晚上没有任何作业将被执行。我们的日志文件中没有异常或日志条目。

有人知道为什么会出现这种情况吗?或者如何获取更多关于这个问题的信息?

是否有一种方法可以在应用程序内部检测到这种情况并重新启动调度器?

目前,我们通过每5分钟运行一次记录作业并创建日志条目的方式来解决这个问题。如果日志文件停止更新(由nagios监控),我们就知道是时候重新启动Tomcat了。但希望能够重新启动作业而不需要完全重启服务器。


7
在预定的任务中正在进行哪些工作?有可能会陷入无限循环吗?我问这个问题是因为预定的任务默认使用1个线程的线程池,如果它在某种情况下被挂起了,你未来的任务将不会启动(但我相信它们会被排队)。 - nicholas.hauschild
@nicholas.hauschild 它调用了一个外部的REST webservice。所以你的意思是这样一个请求可能会阻塞(死锁?),从而停止所有其他作业。如果再次发生这种情况,我想我会请求服务器的线程转储。感谢您的建议。 - obecker
获取线程转储可能是一个好主意。 - nicholas.hauschild
需要考虑的事项 1 - 对 REST 服务的调用执行超时限制,甚至可以在单独的线程中生成该调用,并在指定时间内无响应时终止它。 - Steve
需要考虑的事情2 - 从Web应用程序外部控制调度。这样做往往更可靠/可控。也许可以看一下Spring Batch作为控制和监视作业的手段。 - Steve
如果是在Tomcat中,你是否已经检查了localhost.log?通常一些未捕获的异常会在那里出现。另外,你可能需要启用调度程序的continueScheduledExecutionAfterException选项。 - hsluo
3个回答

29

由于这个问题得到了很多投票,我将发布我的问题的(可能非常具体)解决方案。

我们使用Apache HttpClient库在计划任务中调用远程服务。不幸的是,在执行请求时没有设置默认超时时间。在设置后

connectTimeout
connectionRequestTimeout
socketTimeout

30秒后问题得以解决。

int timeout = 30 * 1000; // 30 seconds
RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(timeout)
        .setConnectionRequestTimeout(timeout)
        .setSocketTimeout(timeout).build();
HttpClient client = HttpClients.custom()
        .setDefaultRequestConfig(requestConfig).build();

9
我曾经遇到完全相同的问题,甚至使用了Apache HttpClient...你,朋友,是一个有教养的绅士! - Nicholas Terry
1
这确实也是我的问题,具体来说是在使用配置了PoolingHttpClientConnectionManager的ApacheConnector时使用了Jersey。如果不设置connectionRequestTimeout参数,池可能会无限期地挂起,因此这一点至关重要。为此,您必须在RequestConfig中设置它,并将整个请求配置设置在连接器客户端配置中,如下所示:RequestConfig rc = RequestConfig.custom().setConnectTimeout(2000).setSocketTimeout(2000).setConnectionRequestTimeout(200).build(); clientConfig.property(ApacheClientProperties.REQUEST_CONFIG, rc); - David
@David 是正确的,我遇到了同样的情况,我已经设置了 socketTimeout 和 connectTimeout,所以必须设置 connectionRequestTimeout。 - mulya
我不明白为什么这会阻止 @scheduled 任务运行。这难道不只是由于超时导致该单个运行失败吗? - ndtreviv
@ndtreviv 如果上一次的运行还没有完成,调度程序将不会启动新的运行。 - obecker

5

这很容易找出来,您可以通过堆栈跟踪来完成。有许多帖子介绍如何获取堆栈跟踪,在Unix系统中,您可以执行“kill -3”命令,并且堆栈跟踪会显示在catalina.out日志文件中。

一旦您有了堆栈跟踪,请查找调度程序线程并查看它正在执行的操作。是否可能任务卡住了?

您还可以在此处发布堆栈跟踪以获取更多帮助。

重要的是要知道您使用的调度程序。如果您使用SimpleAsyncTaskExecutor,则每个任务都将启动一个新线程,您的调度永远不会失败。但是,如果您有未完成的任务,最终会耗尽内存。

http://docs.spring.io/spring/docs/3.0.x/reference/scheduling.html


谢谢 - nicolas.hausschild已经建议获取线程转储,并且我已经发现REST服务中的阻塞HTTP调用。我已更新HttpClient库,想知道这是否可能已经解决了问题。 - obecker

2
在我的情况下,堆栈跟踪完全干净,线程只启动了几次,这就是全部。问题在于与另一个计划的冲突。
更新:
计划未能正确工作,因为我使用了 fixedDelayString,并且上一个任务在开始新任务时尚未结束。将计划更改为 fixedRateString 后,线程开始正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接