基于Spring Batch 配置重试逻辑

 更新时间:2021年09月08日 08:39:15   作者:梦想画家  
这篇文章主要介绍了Spring Batch 配置重试逻辑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Spring Batch在处理过程中遇到错误job默认会执行失败。为了提高应用程序的健壮性,我们需要处理临时异常造成失败。本文我们探讨如何配置Spring Batch的重试逻辑。

1. 应用示例

批处理应用读取csv文件

sammy, 1234, 31/10/2015, 10000
john, 9999, 3/12/2015, 12321

然后,通过调用rest接口处理每条记录,获取用户的年龄和邮编属性,为了正确输出日期,可以在属性上增加@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)注解:

@XmlRootElement(name = “transactionRecord”)
@Data
public class Transaction {
private String username;
private int userId;
private int age;
private String postCode;
private LocalDateTime transactionDate;
private double amount;
}

处理类如下

public class RetryItemProcessor implements ItemProcessor<Transaction, Transaction> {
    private static final Logger LOGGER = LoggerFactory.getLogger(RetryItemProcessor.class);
    @Autowired
    private CloseableHttpClient closeableHttpClient;
    @Override
    public Transaction process(Transaction transaction) throws IOException, JSONException {
        LOGGER.info("Attempting to process user with id={}", transaction.getUserId());
        HttpResponse response = fetchMoreUserDetails(transaction.getUserId());
        //parse user's age and postCode from response and update transaction
        String result = EntityUtils.toString(response.getEntity());
        JSONObject userObject = new JSONObject(result);
        transaction.setAge(Integer.parseInt(userObject.getString("age")));
        transaction.setPostCode(userObject.getString("postCode"));
        return transaction;
    }
    private HttpResponse fetchMoreUserDetails(int id) throws IOException {
        final HttpGet request = new HttpGet("http://www.testapi.com:81/user/" + id);
        return closeableHttpClient.execute(request);
    }
}

这里当然也可以使用RestTemplate进行调用,调用服务仅为了测试,读者可以搭建测试接口。

最终输出结果为

<transactionRecord>
    <transactionRecord>
        <amount>10000.0</amount>
        <transactionDate>2015-10-31 00:00:00</transactionDate>
        <userId>1234</userId>
        <username>sammy</username>
        <age>10</age>
        <postCode>430222</postCode>
    </transactionRecord>
    ...
</transactionRecord>

2. 给处理增加重试功能

如果连接rest接口因为网络不稳定导致连接超时,那么批处理将失败。但这种错误并不是不能恢复,可以通过重试几次进行尝试。

因此我们配置批处理job在失败的情况下重试三次

@Configuration
@EnableBatchProcessing
public class SpringBatchRetryConfig {
    private static final String[] tokens = { "username", "userid", "transactiondate", "amount" };
    private static final int TWO_SECONDS = 2000;
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Value("input/recordRetry.csv")
    private Resource inputCsv;
    @Value("file:xml/retryOutput.xml")
    private Resource outputXml;
    public ItemReader<Transaction> itemReader(Resource inputData) throws ParseException {
        DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
        tokenizer.setNames(tokens);
        DefaultLineMapper<Transaction> lineMapper = new DefaultLineMapper<>();
        lineMapper.setLineTokenizer(tokenizer);
        lineMapper.setFieldSetMapper(new RecordFieldSetMapper());
        FlatFileItemReader<Transaction> reader = new FlatFileItemReader<>();
        reader.setResource(inputData);
        reader.setLinesToSkip(1);
        reader.setLineMapper(lineMapper);
        return reader;
    }
    @Bean
    public CloseableHttpClient closeableHttpClient() {
        final RequestConfig config = RequestConfig.custom()
          .setConnectTimeout(TWO_SECONDS)
          .build();
        return HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    }
    @Bean
    public ItemProcessor<Transaction, Transaction> retryItemProcessor() {
        return new RetryItemProcessor();
    }
    @Bean
    public ItemWriter<Transaction> itemWriter(Marshaller marshaller) {
        StaxEventItemWriter<Transaction> itemWriter = new StaxEventItemWriter<>();
        itemWriter.setMarshaller(marshaller);
        itemWriter.setRootTagName("transactionRecord");
        itemWriter.setResource(outputXml);
        return itemWriter;
    }
    @Bean
    public Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(Transaction.class);
        return marshaller;
    }
    @Bean
    public Step retryStep(@Qualifier("retryItemProcessor") ItemProcessor<Transaction, Transaction> processor,
      ItemWriter<Transaction> writer) throws ParseException {
        return stepBuilderFactory.get("retryStep")
          .<Transaction, Transaction>chunk(10)
          .reader(itemReader(inputCsv))
          .processor(processor)
          .writer(writer)
          .faultTolerant()
          .retryLimit(3)
          .retry(ConnectTimeoutException.class)
          .retry(DeadlockLoserDataAccessException.class)
          .build();
    }
    @Bean(name = "retryBatchJob")
    public Job retryJob(@Qualifier("retryStep") Step retryStep) {
        return jobBuilderFactory
          .get("retryBatchJob")
          .start(retryStep)
          .build();
    }

这里调用faultTolerant()方法启用重试功能,并设置重试次数和对应异常。

3. 测试重试功能

我们测试场景,期望接口在一定时间内返回年龄和邮编。前两次调用API抛出异常ConnectTimeoutException

第三次成功调用

@RunWith(SpringRunner.class)
@SpringBatchTest
@EnableAutoConfiguration
@ContextConfiguration(classes = { SpringBatchRetryConfig.class })
public class SpringBatchRetryIntegrationTest {
    private static final String TEST_OUTPUT = "xml/retryOutput.xml";
    private static final String EXPECTED_OUTPUT = "src/test/resources/output/batchRetry/retryOutput.xml";
    @Autowired
    private JobLauncherTestUtils jobLauncherTestUtils;
    @MockBean
    private CloseableHttpClient closeableHttpClient;
    @Mock
    private CloseableHttpResponse httpResponse;
    @Test
    public void whenEndpointAlwaysFail_thenJobFails() throws Exception {
        when(closeableHttpClient.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"));
    }
    @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\" }"));
        when(closeableHttpClient.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);
    }
    private JobParameters defaultJobParameters() {
        JobParametersBuilder paramsBuilder = new JobParametersBuilder();
        paramsBuilder.addString("jobID", String.valueOf(System.currentTimeMillis()));
        return paramsBuilder.toJobParameters();
    }
}

job成功执行

从日志可以看到两次失败,最终调用成功。

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

同时也定义了另一个测试,重试多次并失败,抛出异常 ConnectTimeoutException。

4. 总结

本文我们学习如何配置Spring Batch的重试逻辑。通过示例学习配置并机型测试,仅为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot时间格式化的方法小结

    SpringBoot时间格式化的方法小结

    SpringBoot中的时间格式化通常指的是将Java中的日期时间类型转换为指定格式的字符串,或者将字符串类型的时间解析为Java中的日期时间类型,本文小编将给大家详细总结了SpringBoot时间格式化的方法,刚兴趣的小伙伴跟着小编一起来看看吧
    2023-10-10
  • Springboot项目中使用redis的配置详解

    Springboot项目中使用redis的配置详解

    这篇文章主要介绍了Springboot项目中使用redis的配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 基于SpringMVC @RequestMapping的参数和用法

    基于SpringMVC @RequestMapping的参数和用法

    这篇文章主要介绍了SpringMVC @RequestMapping的参数和用法解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 关于Spring Cloud的熔断器监控问题

    关于Spring Cloud的熔断器监控问题

    Turbine是一个聚合Hystrix监控数据的工具,它可将所有相关/hystrix.stream端点的数据聚合到一个组合的/turbine.stream中,从而让集群的监控更加方便,接下来通过本文给大家介绍Spring Cloud的熔断器监控,感兴趣的朋友一起看看吧
    2022-01-01
  • Springboot实现多线程及线程池监控

    Springboot实现多线程及线程池监控

    线程池的监控很重要,本文就来介绍一下Springboot实现多线程及线程池监控,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-01-01
  • 基于Rest的API解决方案(jersey与swagger集成)

    基于Rest的API解决方案(jersey与swagger集成)

    下面小编就为大家带来一篇基于Rest的API解决方案(jersey与swagger集成)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • java selenium处理Iframe中的元素示例

    java selenium处理Iframe中的元素示例

    本文主要介绍java selenium处理Iframe中的元素,这里整理了相关资料并附有示例代码和实现方法,有需要的小伙伴可以参考下
    2016-08-08
  • JAVA正则表达式匹配多个空格的解决方案

    JAVA正则表达式匹配多个空格的解决方案

    这篇文章主要介绍了JAVA正则表达式匹配多个空格的解决方案,文中提到了()和[]本质的区别,本文给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-11-11
  • Java工具之ja-netfilter 2022.1 配置教程

    Java工具之ja-netfilter 2022.1 配置教程

    这篇文章主要介绍了Java工具之ja-netfilter 2022.1 配置教程,本防火墙基于javaagent,所以目前只有基于java的程序能够使用,需要的朋友可以参考下
    2022-04-04
  • Java源码解析阻塞队列ArrayBlockingQueue功能简介

    Java源码解析阻塞队列ArrayBlockingQueue功能简介

    今天小编就为大家分享一篇关于Java源码解析阻塞队列ArrayBlockingQueue功能简介,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01

最新评论