Spring Batch作业重启后执行顺序异常问题咨询
问题背景
现有如下Spring Batch作业配置:
Step stepA = stepBuilderFactory.get(StepName.STEP_A) .tasklet(tasklet1) .listener(stepListener) .build(); Step stepB = stepBuilderFactory.get(StepName.STEP_B) .tasklet(tasklet1) .listener(stepListener) .build(); Step stepC = stepBuilderFactory.get(StepName.STEP_C) .tasklet(tasklet1) .listener(stepListener) .build(); Step stepD = stepBuilderFactory.get(StepName.STEP_D) .tasklet(tasklet2) .listener(stepListener) .build(); Flow flow = new FlowBuilder<SimpleFlow>("flow1") .start(stepA).on(Constant.COMPLETED).to(stepB) .from(stepA).on(Constant.FAILED).fail() .next(stepB).on(Constant.COMPLETED).to(stepC) .from(stepB).on(Constant.FAILED).fail() .next(stepC).on(Constant.COMPLETED).to(decider) .from(stepC).on(Constant.FAILED).fail() .next(decider).on(Constant.CONTINUE).to(stepD) .from(decider).on(Constant.COMPLETED).end() .from(decider).on("*").fail() .next(stepD).on(Constant.COMPLETED).to(stepA) .from(stepD).on(Constant.FAILED).fail() .build(); return jobBuilderFactory.get(JobName.JOB_A) .incrementer(new RunIdIncrementer()) .listener(jobExecutionListener) .start(flow) .end() .build();
首次启动作业执行失败,手动修复问题后使用相同参数重启作业,实际执行链路与预期不符:
- 首次运行链路:
stepA -> stepB -> stepC -> decider(CONTINUE) -> stepD (FAILED) - 第二次重启实际链路:
decider(CONTINUE) -> stepD (COMPLETED) -> decider(CONTINUE) -> stepD (FAILED) - 预期链路:
stepD (COMPLETED) -> stepA -> stepB -> stepC -> decider(CONTINUE) -> stepD -> stepA -> ...
异常原因
问题由两个配置错误共同导致:
- Flow路由配置逻辑错误
Spring Batch的FlowBuilder链式调用中,所有未通过.from(节点)显式锚定来源的路由,都会默认绑定到最近一次声明的源节点。原配置在定义完decider的三个分支(CONTINUE到stepD、COMPLETED结束、*匹配失败)后,直接调用.next(stepD).on(Constant.COMPLETED).to(stepA),这部分路由会被错误绑定到最近的源节点decider上,而非stepD。后续仅定义了stepD失败的路由,未定义stepD成功的出口,stepD执行完成后框架找不到匹配的流转规则,会自动回溯到最近的判断节点decider重新执行,因此出现stepD完成后直接回到decider的现象。 - 重启恢复逻辑的默认行为
Spring Batch重启失败作业时,默认会从失败Step之前最后一个执行成功的状态判断节点开始恢复,而非直接从失败Step启动。首次运行失败在stepD,其上游最后一个判断节点是decider,因此重启时会先重新执行decider确认路由,这就是第二次运行直接从decider开始的原因。
正确配置方案
调整Flow定义,所有节点的路由都通过.from(节点)显式锚定来源,避免隐式上下文导致的路由映射错误,修正后的Flow配置如下:
Flow flow = new FlowBuilder<SimpleFlow>("flow1") .start(stepA) .on(Constant.COMPLETED).to(stepB) .on(Constant.FAILED).fail() .from(stepB) .on(Constant.COMPLETED).to(stepC) .on(Constant.FAILED).fail() .from(stepC) .on(Constant.COMPLETED).to(decider) .on(Constant.FAILED).fail() .from(decider) .on(Constant.CONTINUE).to(stepD) .on(Constant.COMPLETED).end() .on("*").fail() .from(stepD) .on(Constant.COMPLETED).to(stepA) .on(Constant.FAILED).fail() .build();
额外注意事项:
- 循环作业场景下,需要保证decider的判断逻辑是基于持久化的Job上下文实现,同一Job执行实例中相同上下文下decider输出稳定,避免重复执行时路由错乱。
- 如果不希望重启时重复执行decider,可以给decider对应的执行逻辑配置状态持久化,框架重启时会直接读取上次decider的执行结果,不会重新执行判断逻辑。
内容的提问来源于stack exchange,提问作者Nesd
相关产品推荐
相关产品推荐

