Configuration de la logique de relance dans Spring Batch

1. Vue d'ensemble

Par défaut, un travail par lots Spring échoue pour toutes les erreurs soulevées lors de son exécution. Cependant, nous souhaitons parfois améliorer la résilience de notre application pour faire face aux pannes intermittentes.

Dans ce rapide tutoriel, nous allons explorer comment configurer la logique de nouvelle tentative dans le framework Spring Batch .

2. Un exemple de cas d'utilisation

Disons que nous avons un travail par lots qui lit un fichier CSV d'entrée:

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

Ensuite, il traite chaque enregistrement en frappant un point de terminaison REST pour récupérer les attributs age et postCode de l'utilisateur :

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; } ... }

Et enfin, il génère un XML de sortie consolidé :

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

3. Ajout de nouvelles tentatives à ItemProcessor

Maintenant, que se passe-t-il si la connexion au point de terminaison REST expire en raison d'une lenteur du réseau? Si tel est le cas, notre travail par lots échouera.

Dans de tels cas, nous préférerions que le traitement de l'élément ayant échoué soit retenté plusieurs fois. Et donc, configurons notre travail par lots pour effectuer jusqu'à trois tentatives en cas d'échec :

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

Ici, nous avons un appel à faultTolerant () pour activer la fonctionnalité de nouvelle tentative. De plus, nous utilisons retry et retryLimit pour définir les exceptions qui se qualifient pour une nouvelle tentative et le nombre maximal de tentatives pour un élément, respectivement.

4. Test des tentatives

Prenons un scénario de test où le point de terminaison REST retournant l' âge et le postCode étaient en panne juste pendant un moment. Dans ce scénario de test, nous obtiendrons une exception ConnectTimeoutException uniquement pour les deux premiers appels d'API, et le troisième appel réussira:

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

Ici, notre travail s'est terminé avec succès. De plus, il est évident d'après les journaux que le premier enregistrement avec id = 1234 a échoué deux fois et a finalement réussi à la troisième tentative :

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

De même, nous allons avoir un autre cas de test pour voir ce qui se passe lorsque toutes les tentatives sont épuisées :

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

Dans ce cas, trois tentatives ont été tentées pour le premier enregistrement avant que le travail échoue finalement en raison d'une exception ConnectTimeoutException .

5. Configuration des tentatives à l'aide de XML

Enfin, regardons l'équivalent XML des configurations ci-dessus:

6. Conclusion

Dans cet article, nous avons appris à configurer la logique de nouvelle tentative dans Spring Batch. Nous avons examiné les configurations Java et XML.

Nous avons également utilisé un test unitaire pour voir comment les tentatives fonctionnaient dans la pratique.

Comme toujours, l'exemple de code de ce didacticiel est disponible à l'adresse over sur GitHub.