简体   繁体   English

如何以编程方式为spring tasklet(不是块)配置容错

[英]How to configure fault tolerance programmatically for a spring tasklet (not a chunk)

Programmatically configuring fault tolerance for a chunk works kind of as follows:以编程方式为块配置容错的工作方式如下:

stepBuilders.get("step")
  .<Partner,Partner>chunk(1)
  .reader(reader())
  .processor(processor())
  .writer(writer())
  .listener(logProcessListener())
  .faultTolerant()
  .skipLimit(10)
  .skip(UnknownGenderException.class)
  .listener(logSkipListener())
  .build();

The trick is, that with adding "chunk", the chain switches to a SimpleStepBuilder which offers the "faultTolerant" method.诀窍是,通过添加“块”,链切换到提供“容错”方法的 SimpleStepBuilder。

My question is how to do that if you just have a tasklet (no reader, processor, writer)?我的问题是,如果您只有一个 tasklet(没有读取器、处理器、写入器),该怎么做?

Defining a tasklet works as follows:定义 tasklet 的工作方式如下:

stepBuilders.get("step")
  .tasklet(tasklet())
  .build();

The usage of "tasklet" switches to a TaskletStepBuilder which does not offer a "faultTolerant" method. “tasklet”的使用切换到不提供“faultTolerant”方法的 TaskletStepBuilder。 Therefore I see no way how to define properties like skipLimit and that like.因此,我看不出如何定义诸如 skipLimit 之类的属性。

Any ideas?有任何想法吗?

A Tasklet has no notion of "items" to skip, so fault tolerance only makes sense for a chunk-oriented step. Tasklet没有要跳过的“项目”的概念,因此容错仅对面向块的步骤有意义。 I suggest you use Spring Retry in the raw (1.1.0.RELEASE is out now, just, and you have some fluent builder options and a @Retryable annotation there).我建议您在原始版本中使用 Spring Retry(1.1.0.RELEASE 现在已经发布,并且您有一些流畅的构建器选项和@Retryable注释)。

Based on the guidance given by @DaveSyer and using org.springframework.retry:spring-retry:1.1.0.RELEASE this is what I have ended up with:根据@DaveSyer 给出的指导并使用org.springframework.retry:spring-retry:1.1.0.RELEASE这就是我最终得到的结果:

Tasklet tasklet = // whatever
ProxyFactory proxyFactory = new ProxyFactory(Tasklet, new SingletonTargetSource(tasklet));
proxyFactory.addAdvice(RetryInterceptorBuilder.stateless()
                                              .maxAttempts(3)
                                              .build());
Step step = stepBuilderFactory.get("taskName")
                              .tasklet((Tasklet)proxyFactory.proxy)
                              .build();

The only thing I'm struggling with still is if I want to swallow the exception that causes the retry after maxAttempts is exceeded.我唯一仍在苦苦挣扎的是,如果我想吞下导致超过maxAttempts后重试的异常。 If I add an ExceptionHandler to the step then the step is never retried.如果我将ExceptionHandler添加到步骤中,则永远不会重试该步骤。 I guess that means the exception handler is inside the pointcut which I find a little surprising.我想这意味着异常处理程序位于切入点内,我觉得这有点令人惊讶。

//you need to have a bean in order to define the retry policies        
            @Configuration
            @EnableAutoConfiguration
            public class MyRetryBean{

              @Autowired
              private RetryProperties properties;
              private RetryTemplate retryTemplate;

              @Bean
              public RetryTemplate initializeRetryTemplate() {
                Map<Class<? extends Throwable>, Boolean> retryExceptions = Collections.singletonMap(IOException.class,
                      Boolean.TRUE);
                SimpleRetryPolicy policy = new SimpleRetryPolicy(properties.getMaxAttempt(), retryExceptions);
                retryTemplate = new RetryTemplate();
                retryTemplate.setRetryPolicy(policy);
                FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
                backOffPolicy.setBackOffPeriod(properties.getBackoffPeriod());
                retryTemplate.setBackOffPolicy(backOffPolicy);
                return retryTemplate;
              }

              public RetryTemplate getRetryTemplate() {
                return retryTemplate;
              }
            }

// in spring batch steps config

            @Autowire
            private MyRetryBean retryBean;

            stepBuilders.get("step")
              .tasklet(new MyTasklet(retryBean))
              .build();

// in your tasklet

        public class MyTasklet implements Tasklet{

          private MyRetryBean retry;

          public MyTasklet (MyRetryBean aRetry) {
            this.retry= aRetry;
          }

          @Override
          public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws IOException {

            retry.getRetryTemplate().execute(new RetryCallback<RepeatStatus, IOException>() {
              @Override
              public RepeatStatus doWithRetry(RetryContext context) throws IOException {
                throw new IOException();
              }
            });
            return null;
          }
        }

To answer your Question, no there is no support :-(要回答你的问题,不,没有支持:-(

I really like to have the Spring Batch retry support for Tasklet too.我也非常喜欢 Tasklet 的 Spring Batch 重试支持。 And to be honest, i don't see why they implement it.老实说,我不明白他们为什么要实施它。 Calling a unstable System from a Tasklet is a valid use case.从 Tasklet 调用不稳定的系统是一个有效的用例。 I don't want to use the @Retryable as the Spring Batch support for retry is more clearer.我不想使用 @Retryable 因为 Spring Batch 对重试的支持更清晰。 So i came up with this solution:所以我想出了这个解决方案:

  @Bean
    public Step setInboxMessageStatusToReceivedStep(OneItemReader oneItemReader, SetInboxMessageStatusToReceivedItemWriter setInboxMessageStatusToReceivedItemWriter) {
        return this.stepBuilders.get("setInboxMessageStatusToReceivedStep")
                .<String, String>chunk(1)
                .reader(oneItemReader)
                .writer(setInboxMessageStatusToReceivedItemWriter)
                .faultTolerant()
                .backOffPolicy(milliSecondsBetweenRetiesPolicy(this.milliSecondsBetweenReties))
                .retryLimit(this.retryLimit)
                .retry(Throwable.class)
                .build();
    }

/**
 * Spring Batch has some cool things like retry or backOffPolicy. These things are only available for ItemReader + ItemWriter and not for Tasklets.
 * This is a nice Hack to use these Spring Batch features for Tasklets like thinks wrapped into a ItemReader + ItemWriter.
 */
public class OneItemReader implements ItemReader<String> {

    public static final String ONE_ITEM_READER_EXECUTION_CONTEXT_KEY = "ONE_ITEM_READER_EXECUTION_CONTEXT_KEY";
    public static final String JOB_SUCCESSFUL_FINISHED = "ONE_ITEM_READER_EXECUTION_CONTEXT_VALUE_JOB_SUCCESSFUL_FINISHED";
    private StepExecution stepExecution;

    @BeforeStep
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
    }

    @Override
    public String read() {
        String isFirstTimeRead = (String) this.stepExecution.getExecutionContext().get(ONE_ITEM_READER_EXECUTION_CONTEXT_KEY);
        if (isFirstTimeRead == null) {
            return "dummy value just to ensure the writer is invoked";
        } else {
            // null will stop the reading
            return null;
        }
    }

    /**
     * As the last action in a Step, this method must be called. It tells the OneItemReader to stop returning Items.
     */
    public static void markDataAsRead(StepExecution stepExecution) {
        stepExecution.getExecutionContext().put(ONE_ITEM_READER_EXECUTION_CONTEXT_KEY, JOB_SUCCESSFUL_FINISHED);
    }
}

Your ItemWriter can now work just like a Tasklet.您的 ItemWriter 现在可以像 Tasklet 一样工作。 It is only once called.它只被调用一次。

Note, that you have to call markDataAsRead(this.stepExecution);请注意,您必须调用 markDataAsRead(this.stepExecution); at the in for your ItemWriter.在您的 ItemWriter 中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM