Chapter 3. 批处理的领域语言
Spring Batch所使用的批处理概念对于任何有批处理操作的架构师来说都会感到熟悉与舒适,其中有"Jobs","Steps"以及工程师提供的被称为"ItemReader"和"ItemWriter"的批处理单元。然而,由于Spring的模式、操作、模板、回调和术语,还有着以下的方便性:
下图是一个使用了数十年的批处理体系结构的简化版本,它提供了组成批处理操作领域语言的各组件的概要。这个体系框架作为一个蓝图在最近几代平台上提供了数十份实现(COBOL/Mainframe,C++/Unix,Java/anywhere),JCL和COBOL开发者同C++,C#,Java开发者一样熟悉这个概念。Spring Batch为此提供的层次、组件与技术服务的物理实现被验证是健壮的,作为基础服务与扩展服务通常被用于建立从简单到复杂的批处理应用,用来解决十分复杂的处理需求。
图中高亮部分就是组成批处理领域语言的关键。一个Job有多个Step,一个Step对应一个ItemReader、ItemProcessor、ItemWriter。一个Job需要被启动(JobLauncher),当前运行中的流程需要被存储(JobRepository)。
3.1 Job
本节介绍批处理任务的固有观念。一个Job封装了整个批处理过程,同Spring其他的项目一样,Job经由被称为"job configuration"的xml配置文件连结起来。但是,job也只是整个层次结构的顶层:
在Spring Batch中,job只是Step的容器,组合了一个流程中属于一个逻辑下的多个step,也能够进行针对所有step的全局属性配置(如配置可重启)。job的配置包括:
Spring Batch提供了SimpleJob这样一个默认的简单job接口的实现形式,在job的顶层建立了标准功能,然而有时批处理的名空间概念需要直接实例化,这是就可以使用
3.1.1 JobInstance
JobInstance涉及到一个逻辑job运行的概念。使用一个在一天结束时立即运行的批处理任务作为例子,就像之前图表中的"EndOfDay"任务。有一个统一的"EndOfDay"任务,但是job的每个独立运行都必须分开监控。在这个例子中,每天会有一个逻辑的JobInstance。例如,在1月1日运行一次,在1月2日运行一次。如果1月1日第一次运行失败了,第二天再运行一次,这仍然是1月1日的运行。(通常也和处理的数据对应起来,意味着处理的是1月1日的数据)。因此,每个JobInstance能有多次执行(JobExection下文再讨论),而对应于在特定时间运行特定的Job和JobParameter只有一个JobInstance。
JobInstance的定义完全与如何加载的数据无关,数据的加载方式只与ItemReader的实现方式相关。例如在EndOfDay场景中,可能有一个名为"effective data"或是"schedule data"的数据列用来表示数据所属日期。因此,1月1日运行只会加载属于1日的数据,1月2日运行只会加载2日的数据。因为这更像是一个业务决定,所以留给ItemReader去处理。另外,使用相同的JobInstance可以决定是否使用前一次执行使用的状态(例如ExecutionContext,下文讨论)。使用新的JobInstance意味着“从头开始”,而使用存在的instance通常意味着"从离开的地方开始"。
3.1.2 JobParameters
在上面的例子中一个job有两个实例,一个是1月1日以01-01-2008的参数启动运行,一个是1月2日以01-02-2008的参数启动运行。因此可以这样定义:JobInstance = Job + JobParameter。这让开发者有效地控制JobInstance,因为开发者控制输入给JobInstance的参数。
3.1.3 JobExecution
一个JobExecution是运行一次Job。一次执行可能成功也可能失败,但是只有这次执行完全成功后对应的JobInstance才会被认为是完成了。以之前的EndOfDay任务为例,01-01-2008第一次运行生成的JobInstance失败后,以相同的参数(01-01-2008)再次运行,一个新的JobExecution会被创建,但是仍然是同一个JobInstance。
Job定义了任务是什么以及如何启动,JobInstance是纯粹的组织对象用来执行操作组织在一起,能够开启正确的重启语义,而JobExecution是运行过程中状态的主要存储机制,有着多得多的属性需要控制与持久化:
Table 3.1 JobExecution属性
status | BatchStatus对象表示了执行状态。BatchStatus.STARTED表示运行时,BatchStatus.FAILED表示执行失败,BatchStatus.COMPLETED表示任务成功结束 |
startTime | 使用java.util.Date表示任务开始时的系统时间 |
endTime | 使用java.util.Date表示任务结束时的系统时间 |
exitStatus | ExitStatus表示任务的运行结果。它是最重要的,因为它包含了返回给调用者的退出代码。可以查看第五章的细节信息 |
createTime | 使用java.util.Date表示JobExecution第一次持久化时的系统时间。这是框架管理任务的ExecutionContext所要求的,一个任务可能还没有启动(也就没有startTime),但总是会有createTime |
lastUpdated | 使用java.util.Date表示最近一次JobExecution被持久化的系统时间 |
executionContext | ‘属性包'包含了运行过程中所有需要被持久化的用户数据。 |
failureException | 在任务执行过程中例外的列表。在任务失败时有不止一个例外发生的情况下会很有用。 |
这些属性是很重要的被用于判断任务执行的状态。例如,如果EndOfDay在01-01的下午9点启动,在9:30时失败了,那么在批处理元数据表中会创建下面的记录:
Table 3.2 BATCH_JOB_INSTANCE
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDay |
Table 3.3 BATCH_JOB_PARAMS
JOB_INST_ID | TYPE_CD | KEY_NAME | DATE_VAL |
---|---|---|---|
1 | DATE | schedule.Date | 2008-01-01 |
Table 3.4 BATCH_JOB_EXECUTION
JOB_EXE_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
1 | 1 | 2008-01-01 21:00 | 2008-01-01 21:30 | FAILED |
现在任务失败了,花费了一整夜解决问题,但是批处理开放时间已经过去了。假定批处理开放时间是下午9:00开始,那么01-01的任务会重新开始,到9:30结束。但是由于现在已经是第二天了,01-02的任务也必须在之后的9:31开始运行,正常运行一个小时候在10:30结束。除非两个job可能访问相同的数据,在数据库层面造成冲突锁,一般并不会要求一个JobInstance在另一个之后运行。一个Job什么时候能够运行完全是调度程序决定的,因此对于不同的JobInstance,Spring Batch并不会组织并发执行(如果一个JobInstance在运行时尝试同时运行相同的JobInstance则会抛出JobExecutionAlreadyRunningException例外)。此时JobInstance表和JobParameters表会增加一行数据,JobExecution表会增加两行数据:
Table 3.5 BATCH_JOB_INSTANCE
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDay |
2 | EndOfDay |
Table 3.6 BATCH_JOB_PARAMS
JOB_INST_ID | TYPE_CD | KEY_NAME | DATE_VAL |
---|---|---|---|
1 | DATE | schedule.Date | 2008-01-01 |
2 | DATE | schedule.Date | 2008-01-02 |
Table 3.4 BATCH_JOB_EXECUTION
JOB_EXE_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2008-01-01 21:00 | 2008-01-01 21:30 | FAILED |
2 | 1 | 2008-01-02 21:00 | 2008-01-02 21:30 | COMPLETED |
3 | 2 | 2008-01-02 21:31 | 2008-01-02 22:29 | COMPLETED |