【问题标题】:stop Spring Scheduled execution if it hangs after some fixed time如果在某个固定时间后挂起,则停止 Spring Scheduled 执行
【发布时间】:2016-10-07 12:51:20
【问题描述】:

我使用 Spring Framework 的 Scheduled 来安排我的作业使用 cron 每 5 分钟运行一次。但有时我的工作会无限等待外部资源,我不能在那里设置超时。我不能使用fixedDelay,因为以前的进程有时会进入无限等待模式,我必须每 5 分钟刷新一次数据。

所以我在 Spring Framework 的 Scheduled 中寻找任何选项,以在 fixed-time 成功或不成功运行之后停止该进程/线程。

我发现下面的设置为keepAliveTime 初始化了ThreadPoolExecutor 120 秒,我把它放在@Configuration 类中。谁能告诉我这会像我预期的那样工作。

@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
    int coreThreads = 8;
    int maxThreads = 20;
    final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            coreThreads, maxThreads, 120L, 
            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()
    );
    threadPoolExecutor.allowCoreThreadTimeOut(true);

    return threadPoolExecutor;
}

【问题讨论】:

  • 您的代码无法按预期工作。描述有关您的随机悬挂作业的更多详细信息。

标签: java spring spring-boot spring-scheduled


【解决方案1】:

我不确定这是否会按预期工作。实际上,keepAlive 是用于空闲线程的,我不知道您等待资源的线程是否处于空闲状态。此外,只有当线程数大于核心数时,除非您监视线程池,否则您无法真正知道它何时发生。

keepAliveTime - 当线程数大于核心时,这是多余的空闲线程在终止前等待新任务的最长时间。

您可以执行以下操作:

public class MyTask {

    private final long timeout;

    public MyTask(long timeout) {
        this.timeout = timeout;
    }

    @Scheduled(cron = "")
    public void cronTask() {
        Future<Object> result = doSomething();
        result.get(timeout, TimeUnit.MILLISECONDS);
    }

    @Async
    Future<Object> doSomething() {
        //what i should do
        //get ressources etc...
    }
}

别忘了加@EnableAsync

通过实现 Callable 也可以在没有 @Async 的情况下做同样的事情。

编辑:请记住,它会等到超时,但运行任务的线程不会被中断。当 TimeoutException 发生时,您将需要调用 Future.cancel。并在任务中检查 isInterrupted() 以停止处理。如果您正在调用 api,请确保选中 isInterrupted()。

【讨论】:

  • 这无济于事。主题启动器在他的任务代码中存在一些问题,导致永远随机阻塞工作线程。我在回答中写到了它。如果不解决它 - 池中的所有线程(无论是调度池还是异步池)迟早都会耗尽(并且不要忘记每个阻塞线程对堆栈的内存消耗)。
  • 您可能是对的,但我们没有任何信息。并且 OP 询问他如何使任务超时。
【解决方案2】:

allowCoreThreadTimeOuttimeout 设置无济于事,因为它只是允许工作线程在一段时间没有工作后结束(参见 javadocs)

您说您的工作无限期地等待外部资源。我确定这是因为您(或您使用的某些第三方库)默认使用无限超时的套接字。 还要记住,当 jvm 在 socket.connect/read 上阻塞时,它会忽略 Thread.interrupt()。

所以找出你的任务中使用的女巫套接字库(以及它是如何使用的)并更改它的默认超时设置。

例如:RestTemplate 在 Spring 内部被广泛使用(在 rest 客户端、spring social、spring security OAuth 等)。并且有 ClientHttpRequestFactory 实现来创建 RestTemplate 实例。默认情况下,spring 使用 SimpleClientHttpRequestFactory,它使用 JDK 套接字。默认情况下,它的所有超时都是无限的。

因此,找出你冻结的确切位置,阅读它的文档并正确配置它。

附:如果您没有足够的时间并且“感觉很幸运”,请尝试通过设置 jvm 属性 sun.net.client.defaultConnectTimeoutsun.net.client.defaultReadTimeout 到一些合理的值(有关详细信息,请参阅docs

【讨论】:

  • 如果您使用的是java mail,可以设置以下2个配置。 mail.pop3.connectiontimeout mail.pop3.timeout
  • 好的cmets,真的很有帮助。
【解决方案3】:

keepAliveTime 仅用于清理一段时间内不需要的工作线程 - 它对提交给执行程序的任务的执行时间没有任何影响。

如果需要时间考虑中断,您可以启动一个新线程并在超时后加入它,如果它没有及时完成则中断它。

public class SomeService {

    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void doSomething() throws InterruptedException {
        Thread taskThread = new TaskThread();
        taskThread.start();
        taskThread.join(120 * 000);
        if(taskThread.isAlive()) {
            // We timed out
            taskThread.interrupt();
        }
    }

    private class TaskThread extends Thread {

        public void run() {
            // Do the actual work here
        }
    }
}

【讨论】:

  • 这无济于事。我在回答中写到了它。套接字连接/读取上阻塞的线程忽略中断。只有 socket.close 可以提供帮助。但这一切都发生在第三方代码的深处,因此 TS 无法关闭套接字,因为 hi 并不“拥有”它。
  • 您的全部批评都是基于您对导致挂断的原因所做的猜测。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
相关资源
最近更新 更多