【问题标题】:Restart step (or job) after timeout occours发生超时后重新启动步骤(或作业)
【发布时间】:2019-03-07 17:13:12
【问题描述】:

是否可以在超时时自动重新启动作业或步骤? 我尝试了 retry 和 skip(跳过,因为作业每 30 分钟重新运行一次,前提是没有发生错误)一个步骤,如下所示:

<step id="jobTest.step1">
  <tasklet>
    <transaction-attributes timeout="120"/>
    <chunk reader="testReader" processor="testProcessor" writer="testWriter" commit-interval="10"  retry-limit="3" >
      <retryable-exception-classes>
        <include class="org.springframework.transaction.TransactionTimedOutException"/>
       </retryable-exception-classes>
    </chunk>
    <listeners>
      <listener ref="stepListener" />
    </listeners>
  </tasklet>
</step>

我也尝试了 skip-policy,但没有得到满意的结果。 我只需要在发生超时时重新启动这一步(或整个作业)。

更新

我也试过这个,但没有成功: Spring batch: Retry job if does not complete in particular time

【问题讨论】:

    标签: spring spring-batch


    【解决方案1】:

    重试/跳过功能适用于容错面向块的步骤中的块内的项目,而不是在步骤级别或作业级别。您的要求实际上有两个不同的东西:

    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 次作业。

    希望这会有所帮助。

    【讨论】:

    • 这个替代方案对我来说效果很好。但是,对于我来说,理想的情况是在 TransactionTimedOutException 发生超时时强制重试(或跳过)(tasklet 超时)。
    • @Mahmoud Ben Hassine - 这是否会确保作业/步骤将在它停止的位置重新启动,并显示“2.如何在超时后自动重新启动作业?”
    猜你喜欢
    • 2021-07-15
    • 1970-01-01
    • 1970-01-01
    • 2020-12-20
    • 1970-01-01
    • 1970-01-01
    • 2019-01-05
    • 1970-01-01
    • 2010-12-07
    相关资源
    最近更新 更多