Конфигуриране на повторна логика в Spring Batch

1. Общ преглед

По подразбиране пролетното пакетно задание се проваля за грешки, възникнали по време на изпълнението му. Понякога обаче може да искаме да подобрим устойчивостта на нашето приложение за справяне с периодични повреди.

В този бърз урок ще проучим как да конфигурираме логика за повторен опит в рамката Spring Batch .

2. Примерен случай на употреба

Да приемем, че имаме групова работа, която чете входен CSV файл:

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

След това обработва всеки запис, като удря REST крайна точка, за да извлече атрибутите възраст и postCode на потребителя :

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(); }

Тук имаме повикване към faultTolerant () за активиране на функцията за повторен опит. Освен това използваме retry и retryLimit, за да дефинираме изключенията, които отговарят на условията за повторен опит и съответно максималния брой повторни опити за елемент.

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

Нека имаме тестов сценарий, при който REST крайната точка, връщаща възрастта и postCode, е била намалена само за известно време. В този тестов сценарий ще получим 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 Batch. Разгледахме конфигурациите на Java и XML.

Използвахме и единичен тест, за да видим как опитите работят на практика.

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