You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch中Partition Step的重启问题及去重需求

问题分析与解决方案

一、为什么@Retryable未触发重试?

你用@Retryable标注业务方法,但日志显示失败是在PartitionStep层面抛出的JobExecutionException: Partition handler returned an unsuccessful step——这个异常是分区步骤整体执行失败时抛出的,并非你的ItemReader/业务方法抛出的异常,所以方法级的@Retryable不会生效。

要让Partition步骤支持重试,需要在Step配置中显式添加重试策略,示例如下:

@Bean
public Step myPartitionStep(PartitionHandler partitionHandler) {
    return stepBuilderFactory.get("myPartitionStep")
            .partitioner("slaveStep", myPartitioner())
            .partitionHandler(partitionHandler)
            .faultTolerant()
            .retry(JobExecutionException.class) // 指定要重试的异常类型
            .retryLimit(3) // 重试次数
            .build();
}

二、实现精准重启+无重复数据的核心方案

Spring Batch本身支持作业重启,但要结合你的场景(分区步骤、REST取数、文件写入无重复),需要做以下配置:

1. 确保JobRepository持久化(基础前提)

生产环境必须将JobRepository配置为使用数据库(而非默认的内存存储),这样作业的执行状态、每个分区的执行记录、ExecutionContext才能持久化,重启时才能恢复到停止位置。

2. 分区步骤的重启配置

  • 确保每个分区的执行状态被独立追踪:Spring Batch的PartitionStep默认会记录每个Slave Step的执行状态,重启时只会重新执行失败的分区,而非全部重新跑。
  • 确认Step的allowStartIfComplete=false(默认值,无需修改),保证已成功的分区不会被重复执行。

3. 实现幂等的ItemReader(从停止位置续读)

因为是通过REST调用取数,需要让ItemReader能记录已读取的位置,并在重启时从该位置继续:

  • 在ItemReader的open(ExecutionContext executionContext)方法中,读取之前保存的偏移量(比如最后读取的ID、分页页码);
  • 在update(ExecutionContext executionContext)方法中,实时更新当前读取的偏移量到ExecutionContext,该上下文会被JobRepository持久化;
  • 调用REST接口时,传递偏移量参数,确保只读取未处理的数据。

示例伪代码:

public class RestItemReader implements ItemReader<Data>, StepExecutionListener {
    private StepExecution stepExecution;
    private Long lastProcessedId = 0L;
    private RestTemplate restTemplate;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
        // 从ExecutionContext读取上次停止的位置
        if (stepExecution.getExecutionContext().containsKey("lastProcessedId")) {
            lastProcessedId = stepExecution.getExecutionContext().getLong("lastProcessedId");
        }
    }

    @Override
    public Data read() throws Exception {
        // 调用REST接口,从lastProcessedId之后取数
        List<Data> dataList = restTemplate.getForObject("/api/data?afterId={id}", List.class, lastProcessedId);
        if (CollectionUtils.isEmpty(dataList)) {
            return null;
        }
        Data data = dataList.get(0);
        lastProcessedId = data.getId();
        // 更新ExecutionContext
        stepExecution.getExecutionContext().put("lastProcessedId", lastProcessedId);
        return data;
    }
}

4. 保证FlatFileItemWriter无重复写入

因为文件不允许重复,推荐采用临时文件+合并的方案:

  • 每个分区的Slave Step写入独立的临时文件(文件名包含分区ID);
  • 新增一个后续步骤,在所有分区执行成功后,将所有临时文件合并为最终文件;
  • 如果某个分区失败,重启时只会重新生成该分区的临时文件,合并时覆盖旧的临时文件,最终文件不会出现重复数据。

如果必须直接写入同一个文件,需要:

  • 在Writer中记录已写入的最后一条数据标识(比如ID),并保存到ExecutionContext;
  • 重启时,跳过已写入的记录,只写入新数据;
  • 注意:文件写入不支持事务回滚,所以这种方案需要确保Writer的幂等性,比如写入前检查记录是否已存在(但性能较差),更推荐临时文件方案。

5. 排查Sidecar重启根源

要避免后续再出现类似问题,需要排查nginx sidecar重启的原因:

  • 查看OpenShift的事件日志(oc get events),检查是否有资源不足(CPU/内存超限)、健康检查失败、配置错误等提示;
  • 查看nginx sidecar的日志,确认重启前是否有异常请求或连接问题。

内容的提问来源于stack exchange,提问作者TiredSoul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 13:45:39