当前版本仍在开发中,尚不被视为稳定版本。最新稳定版请使用 Spring Batch 文档 6.0.2!

为重启配置 Step

在“配置并运行 Job”一节中,我们已经讨论过如何重启 Job。重启会对 step 产生很多影响,因此通常也需要一些针对性的配置。

设置启动次数上限

在很多场景下,你会希望控制某个 Step 可被启动的次数。 例如,你可能需要把某个特定的 Step 配置为只能运行一次, 因为它会使某些资源失效,而这些资源必须在下次运行前由人工修复。 这项能力是在 step 级别进行配置的,因为不同 step 的要求可能不同。 一个只能执行一次的 Step,完全可以和另一个可无限次运行的 Step 同时存在于同一个 Job 中。

  • Java

  • XML

下面的代码片段展示了如何在 Java 中配置启动次数上限:

Java Configuration
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10).transactionManager(transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.startLimit(1)
				.build();
}

下面的代码片段展示了如何在 XML 中配置启动次数上限:

XML Configuration
<step id="step1">
    <tasklet start-limit="1">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

前面示例中的 step 只能运行一次。如果再次尝试运行它,就会抛出 StartLimitExceededException。需要注意的是,start-limit 的默认值是 Integer.MAX_VALUE

重启已完成的 Step

对于一个支持重启的 job,可能会有一个或多个 step 无论第一次是否执行成功,都应该始终重新运行。 例如,一个校验 step,或者一个在处理前负责清理资源的 Step,都可能属于这种情况。 在重启后的作业正常执行过程中,凡是状态为 COMPLETED 的 step (也就是已经成功完成过的 step)都会被跳过。将 allow-start-if-complete 设置为 true 可以覆盖这一默认行为,使该 step 每次都执行。

  • Java

  • XML

下面的代码片段展示了如何在 Java 中定义一个可重启作业:

Java Configuration
@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("step1", jobRepository)
				.<String, String>chunk(10).transactionManager(transactionManager)
				.reader(itemReader())
				.writer(itemWriter())
				.allowStartIfComplete(true)
				.build();
}

下面的代码片段展示了如何在 XML 中定义一个可重启作业:

XML Configuration
<step id="step1">
    <tasklet allow-start-if-complete="true">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

Step 重启配置示例

  • Java

  • XML

下面的 Java 示例展示了如何配置一个包含可重启 step 的 job:

Java Configuration
@Bean
public Job footballJob(JobRepository jobRepository, Step playerLoad, Step gameLoad, Step playerSummarization) {
	return new JobBuilder("footballJob", jobRepository)
				.start(playerLoad)
				.next(gameLoad)
				.next(playerSummarization)
				.build();
}

@Bean
public Step playerLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("playerLoad", jobRepository)
			.<String, String>chunk(10).transactionManager(transactionManager)
			.reader(playerFileItemReader())
			.writer(playerWriter())
			.build();
}

@Bean
public Step gameLoad(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("gameLoad", jobRepository)
			.allowStartIfComplete(true)
			.<String, String>chunk(10).transactionManager(transactionManager)
			.reader(gameFileItemReader())
			.writer(gameWriter())
			.build();
}

@Bean
public Step playerSummarization(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	return new StepBuilder("playerSummarization", jobRepository)
			.startLimit(2)
			.<String, String>chunk(10).transactionManager(transactionManager)
			.reader(playerSummarizationSource())
			.writer(summaryWriter())
			.build();
}

下面的 XML 示例展示了如何配置一个包含可重启 step 的 job:

XML Configuration
<job id="footballJob" restartable="true">
    <step id="playerload" next="gameLoad">
        <tasklet>
            <chunk reader="playerFileItemReader" writer="playerWriter"
                   commit-interval="10" />
        </tasklet>
    </step>
    <step id="gameLoad" next="playerSummarization">
        <tasklet allow-start-if-complete="true">
            <chunk reader="gameFileItemReader" writer="gameWriter"
                   commit-interval="10"/>
        </tasklet>
    </step>
    <step id="playerSummarization">
        <tasklet start-limit="2">
            <chunk reader="playerSummarizationSource" writer="summaryWriter"
                   commit-interval="10"/>
        </tasklet>
    </step>
</job>

前面的示例配置描述的是一个用于加载橄榄球比赛信息并进行汇总的 job。 它包含三个 step:playerLoadgameLoadplayerSummarization。 其中 playerLoad 负责从平面文件中加载球员信息,gameLoad 负责加载比赛信息, 最后的 playerSummarization 则根据已加载的比赛数据汇总每位球员的统计结果。 这里假设 playerLoad 加载的文件只需要导入一次, 而 gameLoad 则需要在某个目录中不断加载新增的比赛文件,并在成功导入数据库后将它们删除。 因此,playerLoad step 不需要额外配置;它可以被多次启动,但如果已经完成就会被跳过。 而 gameLoad step 则必须每次都运行,因为上次执行后目录中可能又新增了文件。 所以它将 allow-start-if-complete 设置为 true,以确保总是启动。 (这里假设比赛数据导入的数据库表中带有处理标记,以便汇总 step 能正确识别新导入的数据。) 最关键的汇总 step playerSummarization 则被配置了 2 次的启动上限。 这样做的好处是:如果该 step 持续失败,那么控制作业执行的运维方就会得到一个新的退出码, 而在人工介入之前,该 step 将不能再次启动。

这里的 job 只是用于文档说明示例,并不是 samples 项目中的那个 footballJob

本节剩余内容描述了 footballJob 示例在三次运行中的具体行为。

第 1 次运行:

  1. playerLoad 运行并成功完成,向 PLAYERS 表中写入了 400 名球员。

  2. gameLoad 运行并处理了 11 个文件中的比赛数据,将其内容加载到 GAMES 表中。

  3. playerSummarization 开始处理,并在 5 分钟后失败。

第 2 次运行:

  1. playerLoad 不再运行,因为它已经成功完成过,而 allow-start-if-completefalse(默认值)。

  2. gameLoad 再次运行,又处理了 2 个文件,并将其内容同样加载到 GAMES 表中 (同时设置处理标记,表示这些数据尚未被汇总处理)。

  3. playerSummarization 开始处理所有剩余比赛数据 (通过处理标记进行筛选),并在 30 分钟后再次失败。

第 3 次运行:

  1. playerLoad 不再运行,因为它已经成功完成过,而 allow-start-if-completefalse(默认值)。

  2. gameLoad 再次运行,又处理了 2 个文件,并将其内容同样加载到 GAMES 表中 (同时设置处理标记,表示这些数据尚未被汇总处理)。

  3. playerSummarization 不会被启动,作业会立即失败, 因为这是 playerSummarization 的第 3 次执行,而它的上限只有 2。 要么提高这个上限,要么把该 Job 作为一个新的 JobInstance 来执行。