Рубрики
Без рубрики

Настройка логики повторных попыток в пакете Spring

Spring Batch позволяет нам устанавливать стратегии повторных попыток для задач, чтобы они автоматически повторялись при возникновении ошибки. Здесь мы увидим, как его настроить.

Автор оригинала: Shubhra Srivastava.

1. Обзор

По умолчанию пакетное задание Spring завершается неудачно из-за ошибок, возникших во время его выполнения. Однако иногда мы можем захотеть повысить отказоустойчивость нашего приложения, чтобы справиться с периодическими сбоями.

В этом кратком руководстве мы рассмотрим, как настроить логику повторных попыток в Spring Batch framework .

2. Пример Использования

Допустим, у нас есть пакетное задание, которое считывает входной CSV-файл:

username, userid, transaction_date, transaction_amount
sammy, 1234, 31/10/2015, 10000
john, 9999, 3/12/2015, 12321

Затем он обрабатывает каждую запись, нажимая конечную точку REST, чтобы получить атрибуты пользователя возраст и Почтовый индекс :

public class RetryItemProcessor implements ItemProcessor {
    
    @Override
    public Transaction process(Transaction transaction) throws IOException {
        log.info("RetryItemProcessor, attempting to process: {}", transaction);
        HttpResponse response = fetchMoreUserDetails(transaction.getUserId());
        //parse user's age and postCode from response and update transaction
        ...
        return transaction;
    }
    ...
}

И, наконец, он генерирует консолидированный вывод XML :


    
        10000.0
        2015-10-31 00:00:00
        1234
        sammy
        10
        430222
    
    ...

3. Добавление повторных попыток в ItemProcessor

Теперь, что делать, если время подключения к конечной точке REST истекло из-за некоторой медлительности сети? Если это так, то наше пакетное задание завершится неудачей.

В таких случаях мы бы предпочли, чтобы неудачная обработка элемента была повторена несколько раз. Итак, давайте настроим наше пакетное задание на выполнение до трех повторных попыток в случае сбоев :

@Bean
public Step retryStep(
  ItemProcessor processor,
  ItemWriter writer) throws ParseException {
    return stepBuilderFactory
      .get("retryStep")
      .chunk(10)
      .reader(itemReader(inputCsv))
      .processor(processor)
      .writer(writer)
      .faultTolerant()
      .retryLimit(3)
      .retry(ConnectTimeoutException.class)
      .retry(DeadlockLoserDataAccessException.class)
      .build();
}

Здесь у нас есть вызов fault Tolerant() для включения функции повтора. Кроме того, мы используем retry и retry Limit для определения исключений, которые имеют право на повторную попытку, и максимального количества повторных попыток для элемента соответственно.

4. Тестирование повторных попыток

Давайте рассмотрим тестовый сценарий, в котором конечная точка REST, возвращающая возраст и почтовый индекс , была отключена только на некоторое время. В этом тестовом сценарии мы получим ConnectTimeoutException только для первых двух вызовов API, а третий вызов будет успешным:

@Test
public void whenEndpointFailsTwicePasses3rdTime_thenSuccess() throws Exception {
    FileSystemResource expectedResult = new FileSystemResource(EXPECTED_OUTPUT);
    FileSystemResource actualResult = new FileSystemResource(TEST_OUTPUT);

    when(httpResponse.getEntity())
      .thenReturn(new StringEntity("{ \"age\":10, \"postCode\":\"430222\" }"));
 
    //fails for first two calls and passes third time onwards
    when(httpClient.execute(any()))
      .thenThrow(new ConnectTimeoutException("Timeout count 1"))
      .thenThrow(new ConnectTimeoutException("Timeout count 2"))
      .thenReturn(httpResponse);

    JobExecution jobExecution = jobLauncherTestUtils
      .launchJob(defaultJobParameters());
    JobInstance actualJobInstance = jobExecution.getJobInstance();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();

    assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
    assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
    AssertFile.assertFileEquals(expectedResult, actualResult);
}

Здесь наша работа была успешно завершена. Кроме того, из журналов видно, что первая запись с id=1234 дважды не удалась и, наконец, удалась при третьей повторной попытке :

19:06:57.742 [main] INFO  o.s.batch.core.job.SimpleStepHandler - Executing step: [retryStep]
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=1234
19:06:57.758 [main] INFO  o.b.batch.service.RetryItemProcessor - Attempting to process user with id=9999
19:06:57.773 [main] INFO  o.s.batch.core.step.AbstractStep - Step: [retryStep] executed in 31ms

Аналогично, давайте проведем еще один тестовый случай, чтобы увидеть, что произойдет, когда все повторные попытки будут исчерпаны :

@Test
public void whenEndpointAlwaysFail_thenJobFails() throws Exception {
    when(httpClient.execute(any()))
      .thenThrow(new ConnectTimeoutException("Endpoint is down"));

    JobExecution jobExecution = jobLauncherTestUtils
      .launchJob(defaultJobParameters());
    JobInstance actualJobInstance = jobExecution.getJobInstance();
    ExitStatus actualJobExitStatus = jobExecution.getExitStatus();

    assertThat(actualJobInstance.getJobName(), is("retryBatchJob"));
    assertThat(actualJobExitStatus.getExitCode(), is("FAILED"));
    assertThat(actualJobExitStatus.getExitDescription(),
      containsString("org.apache.http.conn.ConnectTimeoutException"));
}

В этом случае было предпринято три попытки для первой записи, прежде чем задание окончательно завершилось неудачей из-за ConnectTimeoutException .

5. Настройка Повторных Попыток С Использованием XML

Наконец, давайте рассмотрим XML-эквивалент приведенных выше конфигураций:


    
        
            
                
                    
                    
                
            
        
    

6. Заключение

В этой статье мы узнали, как настроить логику повторных попыток в пакете Spring. Мы рассмотрели конфигурации Java и XML.

Мы также использовали модульный тест, чтобы увидеть, как повторные попытки работают на практике.

Как всегда, пример кода для этого урока доступен на GitHub.