|
对于最新稳定版本,请使用Spring Batch Documentation 6.0.0! |
批处理的领域语言
对于任何有经验的批处理架构师来说,批处理的总体概念包括
春季批次应该熟悉且舒适。有“工作”和“步骤”,以及
开发者提供的处理单元称为物品阅读器和物品写手.然而
由于 Spring 的模式、作、模板、回调和习语,存在
以下机会:
-
在明确的关切分离上有显著改善。
-
明确划分的架构层和作为接口提供的服务。
-
简单且默认的实现,允许快速采用和易用 直接开箱。
-
显著提升了可扩展性。
下图是批处理参考架构的简化版本,表示 已经使用了几十年。它概述了构成 批处理的领域语言。该架构框架是一个蓝图,具有 经过数十年的实现,在过去几代 平台(大型机上的COBOL,Unix上的C语言,现在的Java,任意使用)。JCL 和 COBOL 开发者 他们对这些概念的理解可能和C、C#和Java开发者一样熟悉。Spring Batch 提供了层、组件和技术的物理实现 通常存在于用于解决 创建从简单到复杂的批处理应用程序,配备基础设施和扩展 以应对非常复杂的处理需求。
上图突出了构成 领域语言的关键概念
春季批次。一个工作有一个或多个步骤,每个步骤恰好有一个物品阅读器,
一物品处理器,和一物品写手.需要启动一个工作(其中JobLauncher),并且当前进程的元数据需要存储(在JobRepository).
工作
本节描述了与批次作业概念相关的刻板印象。一个工作是
封装整个批处理过程的实体。这与其他春季一样
项目,A工作通过XML配置文件或基于Java的接口连接在一起
配置。这种配置可以被称为“作业配置”。然而工作仅是整体层级的顶端,如下图所示:
在春季批次中,一个工作只是一个容器步实例。它结合了多个
这些步骤逻辑上属于流程中,并允许属性配置
全局覆盖所有步骤,比如可重启性。作业配置包括:
-
工作名称。
-
定义与排序
步实例。 -
无论这份工作是否可以重新开始。
-
Java
-
XML
对于使用 Java 配置的用户,Spring Batch 提供了默认实现
这工作以简易工作类,这会创造某种标准
功能性工作.使用基于Java的配置时,一组
构建器被提供用于实现工作,作为
示例如下:
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.start(playerLoad())
.next(gameLoad())
.next(playerSummarization())
.build();
}
对于使用 XML 配置的用户,Spring Batch 提供了默认实现工作以简易工作类,这会创造某种标准
功能性工作.然而,批次命名空间抽象了
直接实现它。相反,你可以使用<工作>元素,作为
以下示例展示了:
<job id="footballJob">
<step id="playerload" next="gameLoad"/>
<step id="gameLoad" next="playerSummarization"/>
<step id="playerSummarization"/>
</job>
JobInstance
一个JobInstance指的是逻辑作业运行的概念。考虑一个批处理作业,
应在一天结束时运行一次,例如日终 工作摘自前文
图。有一个日终工作,但每一次单独运行工作一定是
单独追踪。就这份工作而言,有一个合理的解释JobInstance每天。
比如,有1月1日的运行,1月2日的运行,依此类推。如果是1月1日
跑步第一次失败,第二天再进行一次,仍然是1月1日的跑步。
(通常,这也对应其处理的数据,即一月份
第一次运行处理1月1日的数据)。因此,每一个JobInstance可以有多个
处决(作业执行本章后面将详细讨论),仅
一JobInstance(对应于一个特定的工作以及识别作业参数)
在特定时间运行。
一个 的定义JobInstance对待加载的数据完全没有影响。
这完全取决于物品阅读器实现以确定数据如何加载。为
例如,在日终在场景中,数据上可能有一列表示生效日期或赛程安排数据所属的。所以,1月1日的播出
只加载1日的数据,1月2日运行仅使用来自
第二。由于这一决定很可能是商业决策,因此由物品阅读器去决定。然而,使用相同的方法JobInstance决定是否
“状态”(即执行上下文,本章后面将讨论)
使用之前的执行。使用新的JobInstance意思是“从
开始“,使用已有实例通常意味着”从你离开的地方开始
怪异“。
作业参数
讨论过JobInstance以及它与工作,这是自然而然的问题
是:“一个人怎么会这样JobInstance与众不同?”答案是:作业参数.一个作业参数对象存储一组用于启动批次的参数
工作。它们可用于识别,甚至在运行过程中作为参考数据,如
以下图片显示:
在前面的例子中,有两个实例,一个是1月1日,另一个是
至于1月2日,实际上只有一个工作但它有两个JobParameter对象:
一个任务参数是2017年1月1日,另一个则是
参数为2017年2月1日。因此,合同可以定义为:JobInstance = 工作+ 识别作业参数.这使得开发者能够有效控制JobInstance定义了,因为它们控制传递的参数。
并非所有作业参数都必须用于识别JobInstance.默认情况下,他们会这样做。不过,该框架也允许提交
一工作其参数不对 的同一性有贡献JobInstance. |
作业执行
一个作业执行指的是一次尝试运行作业的技术概念。一
执行可能失败也可能成功,但JobInstance对应给定的
除非执行成功完成,否则执行不被视为完成。
使用日终 工作之前描述的例子,考虑JobInstance为
2017年1月1日,第一次运行时失败了。如果再用同样的程序运行
将作业参数识别为首次运行(2017年1月1日),一个新的作业执行是
创建。然而,仍然只有一个JobInstance.
一个工作定义了什么是工作以及如何执行,并且JobInstance是
纯粹组织目标将执行分组,主要是为了实现正确的作
重新开始语义。一个作业执行然而,是主要的存储机制
实际发生在一次运行中,并且包含了更多需要控制的属性
并且持续存在,如下表所示:
属性 |
定义 |
|
一个 |
|
一个 |
|
一个 |
|
这 |
|
一个 |
|
一个 |
|
“属性包”包含需要在不同用户之间持久化的用户数据 执行。 |
|
执行过程中遇到的例外列表 |
这些属性很重要,因为它们是持久的,可以被完全利用
确定执行状态。例如,如果日终01-01的职位是
在晚上9:00执行并于9:30失败,批次中会有以下条目
元数据表:
JOB_INST_ID |
JOB_NAME |
1 |
终结工作 |
JOB_EXECUTION_ID |
TYPE_CD |
KEY_NAME |
DATE_VAL |
识别 |
1 |
日期 |
附表。日期 |
2017-01-01 |
true |
JOB_EXEC_ID |
JOB_INST_ID |
START_TIME |
END_TIME |
地位 |
1 |
1 |
2017-01-01 21:00 |
2017-01-01 21:30 |
失败 |
| 为清晰起见,列名可能被简化或删除, 格式。 |
既然任务失败了,假设问题发生了整晚
确定了,因此“批次窗口”现在已经关闭。进一步假设窗户
晚上9点开始,工作将在01-01年再次启动,从上次结束的地方开始,
9:30顺利完成。因为现在是第二天,01-02的工作必须是
同样,比赛在9:31开始,按正常时间结束
整点时间是10:30。没有任何要求必须是JobInstance之后被踢出去
另一种,除非两个作业有可能尝试访问相同的数据,
导致数据库层面的锁定问题。这完全取决于排程员自己决定
确定当工作应该被运行。因为它们是分开的JobInstancesSpring
批次没有试图阻止它们同时运行。(试图运行
相同JobInstance而另一个已经运行,结果为JobExecutionAlreadyRunningException被扔出去)。现在应该会多出一个新的条目
在两者JobInstance和作业参数表格和两个额外的条目作业执行如下表所示:
JOB_INST_ID |
JOB_NAME |
1 |
终结工作 |
2 |
终结工作 |
JOB_EXECUTION_ID |
TYPE_CD |
KEY_NAME |
DATE_VAL |
识别 |
1 |
日期 |
附表。日期 |
2017-01-01 00:00:00 |
true |
2 |
日期 |
附表。日期 |
2017-01-01 00:00:00 |
true |
3 |
日期 |
附表。日期 |
2017-01-02 00:00:00 |
true |
JOB_EXEC_ID |
JOB_INST_ID |
START_TIME |
END_TIME |
地位 |
1 |
1 |
2017-01-01 21:00 |
2017-01-01 21:30 |
失败 |
2 |
1 |
2017-01-02 21:00 |
2017-01-02 21:30 |
完成 |
3 |
2 |
2017-01-02 21:31 |
2017-01-02 22:29 |
完成 |
| 为清晰起见,列名可能被简化或删除, 格式。 |
步
一个步是一个域对象,封装了批次中独立的顺序相位
工作。因此,所有工作完全由一个或多个步骤组成。一个步包含
所有定义和控制实际批处理所需的信息。这
是必然模糊的描述,因为任一给定的内容步在
开发者自行斟酌工作.一个步可以简单或复杂,如
开发者的愿望。一个简单的步可能会将文件中的数据加载到数据库中,
几乎不需要代码(取决于所用实现)。更复杂的步处理过程中可能应用复杂的业务规则。如
其中工作一个步有个体步执行与唯一对应作业执行,如下图所示:
步执行
一个步执行表示一次执行步.一个新的步执行每次步运行方式类似于作业执行.然而,如果某一步失败
执行,因为在失败前一步,没有持久执行。一个步执行只有当它步实际上已经开始了。
步执行由 的对象表示步执行类。每次处决
包含对应步的引用,且作业执行以及与交易相关的
数据,比如提交和回滚计数以及开始和结束时间。此外,每一步
执行包含一个执行上下文,其中包含开发者所需的任何数据
在批处理过程中持续存在,例如统计数据或状态信息
重新启动。下表列出了步执行:
属性 |
定义 |
|
一个 |
|
一个 |
|
一个 |
|
这 |
|
“属性包”包含需要在不同用户之间持久化的用户数据 执行。 |
|
成功阅读的项目数量。 |
|
成功写出的项目数量。 |
|
为执行已提交的交易数量。 |
|
业务交易被 |
|
次数 |
|
次数 |
|
被 |
|
次数 |
执行上下文
一执行上下文表示一组持久化的键值对,且
由框架控制,为开发者提供存储持久化的空间
该状态的作用域为步执行对象或作业执行对象。(对于那些
熟悉Quartz,它与JobDataMap.)最好的用法示例是
促进重启。以平面文件输入为例,处理单个文件时
该框架周期性地持续存在执行上下文在提交点。行为
那么让物品阅读器存储状态以防运行过程中发生致命错误
甚至停电了。只需输入当前的行数
根据上下文进行解读,正如下例所示,框架会
休息:
executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition());
使用日终示例来自工作以刻板印象为例,假设在那里
是一步,loadData,将文件加载到数据库中。在第一次失败的尝试之后,
元数据表的示例如下:
JOB_INST_ID |
JOB_NAME |
1 |
终结工作 |
JOB_INST_ID |
TYPE_CD |
KEY_NAME |
DATE_VAL |
1 |
日期 |
附表。日期 |
2017-01-01 |
JOB_EXEC_ID |
JOB_INST_ID |
START_TIME |
END_TIME |
地位 |
1 |
1 |
2017-01-01 21:00 |
2017-01-01 21:30 |
失败 |
STEP_EXEC_ID |
JOB_EXEC_ID |
STEP_NAME |
START_TIME |
END_TIME |
地位 |
1 |
1 |
loadData |
2017-01-01 21:00 |
2017-01-01 21:30 |
失败 |
STEP_EXEC_ID |
SHORT_CONTEXT |
1 |
{piece.count=40321} |
在前述情况下,步运行30分钟,处理了40,321个“碎片”,其中
在这种情况下,表示文件中的行。该值在每次更新前都会更新
框架提交,可以包含多行对应于执行上下文.在提交前收到通知需要满足以下几项要求StepListener(听音器)实现(或ItemStream),这些内容将被更详细地讨论
本指南后面内容。与前例相同,假设工作是
第二天重新开始。当它重启时,来自执行上下文之
最后一次运行则从数据库中重建。当物品阅读器打开了,它可以
检查上下文中是否有存储状态,然后从那里初始化自己,
如下示例所示:
if (executionContext.containsKey(getKey(LINES_READ_COUNT))) {
log.debug("Initializing for restart. Restart data is: " + executionContext);
long lineCount = executionContext.getLong(getKey(LINES_READ_COUNT));
LineReader reader = getReader();
Object record = "";
while (reader.getPosition() < lineCount && record != null) {
record = readLine();
}
}
在这种情况下,运行完前一码后,当前行数为40,322,令步从中断的地方重新开始。你也可以使用执行上下文为
需要持续记录的统计数据。例如,如果是一个平面銼刀
包含跨多行处理的订单,可能需要
存储已处理的订单数量(这与
行读),以便在步总数为
身体内处理的命令。框架负责为开发者存储这些内容,
用个体正确定位JobInstance.这可能非常困难
知道是否存在执行上下文应该用还是不该用。例如,使用日终如上所述,当01-01段第二次重新开始时,
框架认识到两者是相同的JobInstance以及个人步基础 拉动执行上下文从数据库中取出,并交出(作为步执行) 到步本身。 相反,对于01-02的运行,框架识别它是不同的实例,因此必须将空上下文交给步. 框架为开发者做出许多此类判定,以确保状态在正确的时间被授予。同样重要的是需要注意的是,恰好有一个执行上下文存在于每一个步执行在任何时刻。客户执行上下文应当小心,因为这会形成共享的密钥空间。因此,在输入值时应注意确保没有数据 覆盖。 然而,步上下文中完全不存储任何数据,因此没有任何方式对框架产生不利影响。
请注意,至少有一个执行上下文每作业执行每个步执行. 例如,考虑以下代码片段:
ExecutionContext ecStep = stepExecution.getExecutionContext();
ExecutionContext ecJob = jobExecution.getExecutionContext();
//ecStep does not equal ecJob
正如评论中所述,ecStep不等于ecJob. 它们是两种不同的执行上下文. 那个范围范围为步保存在每个提交点步而作业范围的 则保存在每个步执行。
在执行上下文,所有非瞬态元素必须是序列 化. 执行上下文的正确序列化支持步骤和作业的重启能力。如果你使用了本质上不可序列化的键或值,你必须采用定制化的序列化方法。未能序列化执行上下文可能会危及状态持久化进程,使失败的作业无法正确恢复。 |
JobRepository
JobRepository是上述所有刻板印象的持久机制。它提供了JobLauncher,工作和步实现。 当工作首次发布,a作业执行从仓库中获得。此外,在执行过程中,步执行和作业执行实现是持久化的通过传递到仓库。
-
Java
-
XML
使用 Java 配置时,@EnableBatchProcessing注释提供JobRepository作为自动配置的组件之一。
Spring Batch XML 命名空间支持配置JobRepository实例 其中<job-repository>标签,如下示例所示:
<job-repository id="jobRepository"/>
JobLauncher
JobLauncher代表一个简单的接口,用于启动工作给定的一组作业参数,如下示例所示:
public interface JobLauncher {
public JobExecution run(Job job, JobParameters jobParameters)
throws JobExecutionAlreadyRunningException, JobRestartException,
JobInstanceAlreadyCompleteException, JobParametersInvalidException;
}
期望实现获得有效的作业执行来自JobRepository并执行工作.
物品阅读器
物品阅读器是表示对 A 输入的检索的抽象步一 一次一个。当物品阅读器已用尽其能提供的物品,通过返回表示零. 你可以找到更多关于物品阅读器接口及其在读者与写者中的各种实现。
物品写手
物品写手是表示步,一次一组一组项目。通常,物品写手它不知道它应该接收的输入只知道当前调用中传递的项。你可以找到更多关于物品写手Readers And Writers 中的接口及其各种实现。
物品处理器
物品处理器是表示项目业务处理的抽象。而物品阅读器读出一个项目,且物品写手写入一个项目,为物品处理器提供一个用于转换或应用其他业务处理的访问点。如果在处理该项目时确定该项目无效,返回零表示该项目不应被写出来。你可以找到更多关于物品处理器Readers And Writers中的接口。
批处理命名空间
前面提到的许多领域概念需要在 Spring 中配置应用上下文. 虽然上述接口有实现,但你可以在标准豆定义中使用,但为便于配置,已提供了命名空间如下示例所示:
<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
https://www.springframework.org/schema/batch/spring-batch.xsd">
<job id="ioSampleJob">
<step id="step1">
<tasklet>
<chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
</tasklet>
</step>
</job>
</beans:beans>