当前位置: 首页 > 知识库问答 >
问题:

关闭时停止所有spring批处理作业(CTRL-C)

郑桐
2023-03-14

我有一个spring boot/spring batch应用程序,它启动不同的作业。

当应用程序停止(Ctrl-C)时,作业仍处于运行状态(STARTED)。
尽管Ctrl-C给了应用程序足够的时间来正常地停止作业,但结果与kill-9相同。

我已经找到了一种方法(见下文),可以在应用程序使用Ctrl-C终止时优雅地停止所有作业,但我想知道是否有更好/更简单的方法来实现这一目标。

下面的一切都是关于我如何阻止这些工作的文档。

JobExecutionListener的博客条目中,用于注册应该停止作业的关机钩子:

public class ProcessShutdownListener implements JobExecutionListener {
    private final JobOperator jobOperator;
    ProcessShutdownListener(JobOperator jobOperator) { this.jobOperator = jobOperator; }
     
    @Override public void afterJob(JobExecution jobExecution) { /* do nothing. */ }
 
    @Override
    public void beforeJob(final JobExecution jobExecution) {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    jobOperator.stop(jobExecution.getId());
                    while(jobExecution.isRunning()) {
                        try { Thread.sleep(100); } catch (InterruptedException e) {}
                    }
                } catch (NoSuchJobExecutionException | JobExecutionNotRunningException e) { /* ignore */ }
            }
        });
    }
}
    @Bean
    public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
        JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
        postProcessor.setJobRegistry(jobRegistry);
        return postProcessor;
    }

关闭挂钩无法将状态写入数据库,因为数据库连接已经关闭:org.h2.jdbc.jdbcsqlnontransientConnectionException:数据库已经关闭(若要在VM关闭时禁用自动关闭,请在db URL中添加“;db_close_on_exit=false”)

Processing item 2 before
Shutdown Hook is running !
2021-02-08 22:39:48.950  INFO 12676 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2021-02-08 22:39:49.218  INFO 12676 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
Processing item 3 before
Exception in thread "Thread-3" org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30004ms.

为了确保spring boot在停止作业之前不会关闭hikari datasource池,我使用了SmartLifecycle

最后的processShutdownListener如下所示:

@Component
public class ProcessShutdownListener implements JobExecutionListener, SmartLifecycle {
    private final JobOperator jobOperator;
    public ProcessShutdownListener(JobOperator jobOperator) { this.jobOperator = jobOperator; }

    @Override
    public void afterJob(JobExecution jobExecution) { /* do nothing. */ }

    private static final List<Runnable> runnables = new ArrayList<>();

    @Override
    public void beforeJob(final JobExecution jobExecution) {
        runnables.add(() -> {
                try {
                    if (!jobOperator.stop(jobExecution.getId())) return;
                    while (jobExecution.isRunning()) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException ignored) { /* ignore */ }
                    }
                } catch (NoSuchJobExecutionException | JobExecutionNotRunningException e) { /* ignore */ }
            });
    }

    @Override
    public void start() {}

    @Override
    public void stop() {
//        runnables.stream()
//                .parallel()
//                .forEach(Runnable::run);
        runnables.forEach(Runnable::run);
    }

    @Override
    public boolean isRunning() { return true; }

    @Override
    public boolean isAutoStartup() { return true; }

    @Override
    public void stop(Runnable callback) { stop(); callback.run(); }

    @Override
    public int getPhase() { return Integer.MAX_VALUE; }
}

配置作业时必须注册此侦听器:

    @Bean
    public Job job(JobBuilderFactory jobs,
                   ProcessShutdownListener processShutdownListener) {
        return jobs.get("job1")
                .listener(processShutdownListener)
                .start(step(null))
                .build();
    }

最后,正如异常输出中提到的,必须将标志:;db_close_on_exit=false添加到jdbc URL中。

共有1个答案

督德明
2023-03-14

这种方法是可行的,因为关机钩子是JVM提供的截获外部信号的唯一方法(据我所知)。但是,这种方法不能保证有效,因为JVM不能保证调用关机钩子。以下是runtime.addShutdownHook方法的Javadoc摘录:

In rare circumstances the virtual machine may abort, that is, stop running
without shutting down cleanly. This occurs when the virtual machine is 
terminated externally, for example with the SIGKILL signal on Unix or 
the TerminateProcess call on Microsoft Windows.

而且,关机挂钩有望“快速”运行:

Shutdown hooks should also finish their work quickly. When a program invokes
exit the expectation is that the virtual machine will promptly shut down
and exit.

在您的例子中,joboperator.stop涉及一个数据库事务(可能跨越网络)来更新作业的状态,我不确定这个操作是否足够“快”。

顺便说明一下,samples模块中有一个名为GracefulShutdownFunctionalTests的示例。此示例基于jobexecution.stop,但将更新为使用joboperator.stop

 类似资料:
  • 我已经使用开始我的作业,当我尝试使用另一个请求停止作业时,然后获取exeption: JobExecutionNotrunningException:JobExecution必须正在运行,才能停止 当打印作业状态总是获取但批处理作业正在运行时 它的web应用程序,首先上传一些CSV文件,并使用spring batch启动一些操作,在执行过程中,如果用户需要停止,则从另一个控制器方法来停止请求,并试

  • 我在我的JAVA应用程序中配置了Spring批处理作业,该应用程序在集群中运行。因此,相同的作业被执行两次,这是我不想要的。 所以我想在作业中配置一个步骤,它将检查CREATE_DATE是否在BATCH_JOB_EXECUTION表中存在,并将继续或故障转移。 如何在spring批处理步骤中进行配置?

  • 我正在尝试在后台运行作业,允许我根据某种条件或在超时发生后停止它。 我有这两块代码:

  • 我最近开始使用java配置方式编写spring批处理程序,并使用spring批处理和starter包。我使用了分区的步骤和任务执行器来完成我的工作,我面临的问题是,一旦作业完成,批处理过程就不会停止,它一直在我的eclipse和Linux盒子中运行。我手动找到并终止作业。你能帮个忙吗。当我在没有分区步骤的情况下以单线程的方式运行作业时,这工作很好。 我的作业配置:

  • 我能够通过命令行使用成功地启动springboot-batch作业。

  • 我有以下工作要处理在一定的时间间隔或特别的基础上。 作业中的步骤如下: 我也想要用户界面,在那里我可以触发一个特别的基础上的工作,而且我应该能够提供参数从用户界面。 我想用Spring batch来完成这个任务,但它更多的是用于读->处理->写之类的工作。这里,在第一步中,我正在生成由第二步读取的数据。我不确定我是否还可以使用Spring batch来实现这个,或者有更好的方法来实现这个。