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
相关产品推荐
相关产品推荐

