在关机时停止所有 Spring 批处理作业 (CTRL-C)
Posted
技术标签:
【中文标题】在关机时停止所有 Spring 批处理作业 (CTRL-C)【英文标题】:Stop all spring batch jobs at shutdown (CTRL-C) 【发布时间】:2021-02-08 22:54:28 【问题描述】:我有一个 spring boot / spring batch 应用程序,它启动不同的工作。
当应用停止 (CTRL-C
) 时,作业将保持在运行状态 (STARTED)。
尽管CTRL-C
为应用程序提供了足够的时间来优雅地停止作业,但结果与kill -9
相同。
我找到了一种方法(见下文),可以在使用 CTRL-C
终止应用程序时优雅地停止所有作业,但想知道是否有更好/更简单的方法来实现此目标。
以下所有内容都是关于我如何设法停止工作的文档。
在blog entry from 부알프레도 中,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 */
);
除了提供的代码之外,我还必须创建一个JobRegistryBeanPostProcessor
。
如果没有这个 PostProcessor,jobOperator
将无法找到工作。
(NoSuchJobException: No job configuration with the name [job1] was registered
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry)
JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
postProcessor.setJobRegistry(jobRegistry);
return postProcessor;
关闭钩子无法将状态写入数据库,因为数据库连接已经关闭:
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL)
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 数据源池,我使用了 SmartLifeCycle
,如提到的 here。
最终的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】:这种方法是可行的,因为关闭挂钩是 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
涉及数据库事务(可能跨网络)以更新作业的状态,我不确定此操作是否足够“快速”。
附带说明一下,示例模块中有一个名为GracefulShutdownFunctionalTests 的示例。此示例基于已弃用的JobExecution.stop
,但将更新为使用JobOperator.stop
。
【讨论】:
我们使用nssm在windows上发送信号。使用 task-manager 或 taskkill 会在不执行关闭挂钩的情况下杀死 jvm。nssm.exe install batchApplication D:\java\bin\java -jar spingBatch.jar
nssm 可以向应用发送 CTRL-C。以上是关于在关机时停止所有 Spring 批处理作业 (CTRL-C)的主要内容,如果未能解决你的问题,请参考以下文章
SPRING BATCH:嵌套异常是java.sql.SQLException:ORA-08177:无法序列化此事务的访问权限
Spring 批处理作业应仅在 Spring 集成轮询器之后执行一次