重试/跳过功能适用于容错面向块的步骤中的块内的项目,而不是在步骤级别或作业级别。您的要求实际上有两个不同的东西:
1。如何在给定超时后停止作业?
除了在发生超时后从外部调用JobOperator#stop,您还可以通过StepExecution#isTerminateOnly 标志发送停止信号,从作业本身停止作业。这个想法是可以访问步骤执行,以便在某个超时后设置该标志。这取决于步骤的 tasklet 类型:
简单小任务
对于一个简单的tasklet,您可以通过ChunkContext 访问步骤执行。这是一个例子:
import java.time.Duration;
import java.util.Date;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
public class MyTasklet implements Tasklet {
private static final int TIMEOUT = 120; // in minutes (can be turned into a configurable field through a constructor)
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
if (timeout(chunkContext)) {
chunkContext.getStepContext().getStepExecution().setTerminateOnly();
}
// do some work
if (moreWork()) {
return RepeatStatus.CONTINUABLE;
} else {
return RepeatStatus.FINISHED;
}
}
private boolean timeout(ChunkContext chunkContext) {
Date startTime = chunkContext.getStepContext().getStepExecution().getJobExecution().getStartTime();
Date now = new Date();
return Duration.between(startTime.toInstant(), now.toInstant()).toMinutes() > TIMEOUT;
}
private boolean moreWork() {
return false; // TODO implement logic
}
}
这个 tasklet 会定期检查是否超时,并相应地停止该步骤(以及周围的作业)。
面向块的tasklet
在这种情况下,您可以使用步骤侦听器并在生命周期方法之一(afterRead、afterWrite 等)中设置 terminateOnly 标志。这是一个例子:
import java.time.Duration;
import java.util.Date;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
public class StopListener extends StepListenerSupport {
private static final int TIMEOUT = 120; // in minutes (can be made configurable through constructor)
private StepExecution stepExecution;
@Override
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
@Override
public void afterChunk(ChunkContext context) { // or afterRead, or afterWrite, etc.
if (timeout(context)) {
this.stepExecution.setTerminateOnly();
}
}
private boolean timeout(ChunkContext chunkContext) {
Date startTime = chunkContext.getStepContext().getStepExecution().getJobExecution().getStartTime();
Date now = new Date();
return Duration.between(startTime.toInstant(), now.toInstant()).toMinutes() > TIMEOUT;
}
}
思路是一样的,需要定时查看时间,合适的时候设置flag。
这两种方式都会让您的工作处于STOPPED 状态,这是一个可重新启动的状态。批处理作业过去在批处理窗口中执行,一个常见的要求是在窗口关闭时(优雅地)停止它们。以前的技术是要走的路。
Spring batch: Retry job if does not complete in particular time 中的答案在 IMO 中不是一个好的选择,因为它会突然终止当前块的事务并将作业留在FAILED 状态(这也是可重新启动的状态)。但是,通过查看处于FAILED 状态的作业,无法区分真正的失败和故意停止。鉴于故意希望作业在批处理窗口结束时停止的要求,我认为应该优雅地停止作业并在下一个窗口中重新启动。
2。超时后如何自动重启作业?
现在您知道如何在超时后停止作业,您可以在作业启动器周围使用RetryTemplate,并在适当的时候重新启动作业。这是一个例子:
public static void main(String[] args) throws Throwable {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3));
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyJob.class);
JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class);
Job job = applicationContext.getBean(Job.class);
JobParameters jobParameters = new JobParametersBuilder()
.addDate("runtime", new Date())
.toJobParameters();
retryTemplate.execute((RetryCallback<JobExecution, Throwable>) retryContext -> {
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
if (jobExecution.getExitStatus().getExitCode().equals(ExitStatus.STOPPED.getExitCode())) {
throw new Exception("Job timeout");
}
return jobExecution;
});
}
如果作业以STOPPED 状态结束(例如由于如前所示的超时),这将自动重新运行最多 3 次作业。
希望这会有所帮助。