Configurazione della logica di ripetizione in Spring Batch

1. Panoramica

Per impostazione predefinita, un lavoro batch Spring non riesce per qualsiasi errore generato durante la sua esecuzione. Tuttavia, a volte, potremmo voler migliorare la resilienza della nostra applicazione per far fronte a guasti intermittenti.

In questo breve tutorial, esploreremo come configurare la logica di ripetizione dei tentativi nel framework Spring Batch .

2. Un caso d'uso di esempio

Supponiamo di avere un lavoro batch che legge un file CSV di input:

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

Quindi, elabora ogni record colpendo un endpoint REST per recuperare l' età dell'utente e gli attributi 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; } ... }

Infine, genera un XML di output consolidato :

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

3. Aggiunta di nuovi tentativi a ItemProcessor

Ora, cosa succede se la connessione all'endpoint REST scade a causa di una certa lentezza della rete? In tal caso, il nostro lavoro batch fallirà.

In questi casi, preferiremmo che l'elaborazione dell'articolo non riuscita fosse ripetuta un paio di volte. Quindi, configuriamo il nostro lavoro batch per eseguire fino a tre tentativi in ​​caso di errori :

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

Qui, abbiamo una chiamata a faultTolerant () per abilitare la funzionalità di ripetizione . Inoltre, utilizziamo retry e retryLimit per definire le eccezioni che si qualificano per un nuovo tentativo e il numero massimo di tentativi per un elemento, rispettivamente.

4. Verifica dei tentativi

Facciamo uno scenario di test in cui l'endpoint REST che restituisce età e postCode è rimasto inattivo solo per un po '. In questo scenario di test, otterremo un'eccezione ConnectTimeoutException solo per le prime due chiamate API e la terza chiamata avrà esito positivo:

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

Qui, il nostro lavoro è stato completato con successo. Inoltre, è evidente dai log che il primo record con id = 1234 non è riuscito due volte e alla fine è riuscito al terzo tentativo :

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

Allo stesso modo, facciamo un altro caso di test per vedere cosa succede quando tutti i tentativi sono esauriti :

@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")); }

In questo caso, sono stati tentati tre tentativi per il primo record prima che il lavoro non fosse riuscito a causa di un'eccezione ConnectTimeoutException .

5. Configurazione dei tentativi utilizzando XML

Infine, diamo un'occhiata all'equivalente XML delle configurazioni precedenti:

6. Conclusione

In questo articolo abbiamo appreso come configurare la logica dei tentativi in ​​Spring Batch. Abbiamo esaminato le configurazioni Java e XML.

Abbiamo anche utilizzato uno unit test per vedere come hanno funzionato nella pratica i nuovi tentativi.

Come sempre, il codice di esempio per questo tutorial è disponibile su GitHub.