Spring Batch为批处理提供了一个轻量化的解决方案,它根据批处理的须要迭代处理各类记录,提供事物功能。可是Spring Batch仅仅适用于"脱机"场景,在处理的过程当中不能和外部进行任何交互,也不容许有任何输入。java
如上图,一般状况下一个独立的JVM程序就是仅仅用于处理批处理,而不要和其余功能重叠。 在最后一层基础设置(Infrastructure)部分主要分为3个部分。JobLauncher
、Job
以及Step
。每个Step
又细分为ItemReader
、ItemProcessor
、ItemWirte
。使用Spring Batch主要就是知道每个基础设置负责的内容,而后在对应的设施中实现对应的业务。数据库
当咱们构建一个批处理的过程时,必须注意如下原则:设计模式
一般状况下,批处理的过程对系统和架构的设计要够要求比较高,所以尽量的使用通用架构来处理批量数据处理,下降问题发生的可能性。Spring Batch是一个是一个轻量级的框架,适用于处理一些灵活并无到海量的数据。缓存
批处理应该尽量的简单,尽可能避免在单个批处理中去执行过于复杂的任务。咱们能够将任务分红多个批处理或者多个步骤去实现。服务器
保证数据处理和物理数据紧密相连。笼统的说就是咱们在处理数据的过程当中有不少步骤,在某些步骤执行完时应该就写入数据,而不是等全部都处理完。网络
尽量减小系统资源的使用、尤为是耗费大量资源的IO以及跨服务器引用,尽可能分配好数据处理的批次。数据结构
按期分析系统的IO使用状况、SQL语句的执行状况等,尽量的减小没必要要的IO操做。优化的原则有:架构
不要在批处理中对相同的数据执行2次相同的操做。并发
对于批处理程序而言应该在批处理启动以前就分配足够的内存,以避免处理的过程当中去从新申请新的内存页。框架
对数据的完整性应该从最差的角度来考虑,每一步的处理都应该创建完备的数据校验。
对于数据的总量咱们应该有一个和数据记录在数据结构的某个字段 上。
全部的批处理系统都须要进行压力测试。
若是整个批处理的过程是基于文件系统,在处理的过程当中请切记完成文件的备份以及文件内容的校验。
和软件开发的设计模式同样,批处理也有各类各样的现成模式可供参考。当一个开发(设计)人员开始执行批处理任务时,应该将业务逻辑拆分为一下的步骤或者板块分批执行:
数据转换:某个(某些)批处理的外部数据可能来自不一样的外部系统或者外部提供者,这些数据的结构千差万别。在统一进行批量数据处理以前须要对这些数据进行转换,合并为一个统一的结构。所以在数据开始真正的执行业务处理以前,先要使用其余的方法或者一些批处理任务将这些数据转换为统一的格式。
数据校验:批处理是对大量数据进行处理,而且数据的来源千差万别,因此批处理的输入数据须要对数据的完整性性进行校验(好比校验字段数据是否缺失)。另外批处理输出的数据也须要进行合适的校验(例如处理了100条数据,校验100条数据是否校验成功)
提取数据:批处理的工做是逐条从数据库或目标文件读取记录(records),提取时能够经过一些规则从数据源中进行数据筛选。
数据实时更新处理:根据业务要求,对实时数据进行处理。某些时候一行数据记录的处理须要绑定在一个事物之下。
输出记录到标准的文档格式:数据处理完成以后须要根据格式写入到对应的外部数据系统中。
以上五个步骤是一个标准的数据批处理过程,Spring batch框架为业务实现提供了以上几个功能入口。
某些状况须要实现对数据进行额外处理,在进入批处理以前经过其余方式将数据进行处理。主要内容有:
排序:因为批处理是以独立的行数据(record)进行处理的,在处理的时候并不知道记录先后关系。所以若是须要对总体数据进行排序,最好事先使用其余方式完成。
分割:数据拆分也建议使用独立的任务来完成。理由相似排序,由于批处理的过程都是以行记录为基本处理单位的,没法再对分割以后的数据进行扩展处理。
合并:理由如上。
批处理的数据源一般包括:
在业务中止的窗口期进行批数据处理,例如银行对帐、清结算都是在12点日切到黎明之间。简称为离线处理。
在线或并发批处理,可是须要对实际业务或用户的响应进行考量。
并行处理多种不一样的批处理做业。
分区处理:将相同的数据分为不一样的区块,而后按照相同的步骤分为许多独立的批处理任务对不一样的区块进行处理。
以上处理过程进行组合。
在执行2,3点批处理时须要注意事物隔离等级。
下图是批处理的核心流程图。
(图片来源于网络)
Spring Batch一样按照批处理的标准实现了各个层级的组件。而且在框架级别保证数据的完整性和事物性。
如图所示,在一个标准的批处理任务中涵盖的核心概念有JobLauncher
、Job
、Step
,一个Job
能够涵盖多个Step
,一个Job
对应一个启动的JobLauncher
。一个Step
中分为ItemReader
、ItemProcessor
、ItemWriter
,根据字面意思它们分别对应数据提取、数据处理和数据写入。此外JobLauncher
、Job
、Step
会产生元数据(Metadata),它们会被存储到JobRepository
中。
简单的说Job
是封装一个批处理过程的实体,与其余的Spring项目相似,Job
能够经过XML或Java类配置,称为“Job Configuration”。以下图Job
是单个批处理的最顶层。
为了便于理解,能够简单的将Job
理解为是每一步(Step
)实例的容器。他结合了多个Step
,为它们提供统一的服务同时也为Step
提供个性化的服务,好比步骤重启。一般状况下Job的配置包含如下内容
:
Step
执行实例。Step
是否能够重启。Spring Batch为Job接口提供了默认的实现——SimpleJob
,其中实现了一些标准的批处理方法。下面的代码展现了如可注入一个Job
。
@Bean public Job footballJob() { return this.jobBuilderFactory.get("footballJob") //get中命名了Job的名称 .start(playerLoad()) //playerLoad、gameLoad、playerSummarization都是Step .next(gameLoad()) .next(playerSummarization()) .end() .build(); }
JobInstance
是指批处理做业运行的实例。例如一个批处理必须在天天执行一次,系统在2019年5月1日执行了一次咱们称之为2019-05-01的实例,相似的还会有2019-05-0二、2019-05-03实例。一般状况下,一个JobInstance
对应一个JobParameters
,对应多个JobExecution
。(JobParameters
、JobExecution
见后文)。同一个JobInstance
具备相同的上下文(ExecutionContext
内容见后文)。
前面讨论了JobInstance
与Job
的区别,可是具体的区别内容都是经过JobParameters
体现的。一个JobParameters
对象中包含了一系列Job运行相关的参数,这些参数能够用于参考或者用于实际的业务使用。对应的关系以下图:
当咱们执行2个不一样的JobInstance
时JobParameters
中的属性都会有差别。能够简单的认为一个JobInstance
的标识就是Job
+JobParameters
。
JobExecution
能够理解为单次运行Job
的容器。一次JobInstance
执行的结果多是成功、也多是失败。可是对于Spring Batch框架而言,只有返回运行成功才会视为完成一次批处理。例如2019-05-01执行了一次JobInstance
,可是执行的过程失败,所以第二次还会有一个“相同的”的JobInstance
被执行。
Job
用于定义批处理如何执行,JobInstance
纯粹的就是一个处理对象,把全部的运行内容和信息组织在一块儿,主要是为了当面临问题时定义正确的重启参数。而JobExecution
是运行时的“容器”,记录动态运行时的各类属性和上线文。他包括的信息有:
属性 | 说明 |
---|---|
status | 状态类名为BatchStatus ,它指示了执行的状态。在执行的过程当中状态为BatchStatus#STARTED ,失败:BatchStatus#FAILED ,完成:BatchStatus#COMPLETED |
startTime | java.util.Date 对象,标记批处理任务启动的系统时间,批处理任务未启动数据为空 |
endTime | java.util.Date 对象,结束时间不管是否成功都包含该数据,如未处理完为空 |
exitStatus | ExitStatus 类,记录运行结果。 |
createTime | java.util.Date ,JobExecution 的建立时间,某些使用execution已经建立可是并未开始运行。 |
lastUpdate | java.util.Date ,最后一次更新时间 |
executionContext | 批处理任务执行的全部用户数据 |
failureExceptions | 记录在执行Job时的异常,对于排查问题很是有用 |
以上这些内容Spring Batch都会经过JobRepository
进行持久化(这些信息官方文成称之为MetaData),所以在对应的数据源中能够看到下列信息:
BATCH_JOB_INSTANCE:
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
BATCH_JOB_EXECUTION_PARAMS:
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
---|---|---|---|---|
1 | DATE | schedule.Date | 2019-01-01 | TRUE |
BATCH_JOB_EXECUTION:
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2019-01-01 21:00 | 2017-01-01 21:30 | FAILED |
当某个Job
批处理任务失败以后会在对应的数据库表中路对应的状态。假设1月1号执行的任务失败,技术团队花费了大量的时间解决这个问题到了次日21才继续执行这个任务。
BATCH_JOB_INSTANCE:
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
2 | EndOfDayJob |
BATCH_JOB_EXECUTION_PARAMS:
JOB_EXECUTION_ID | TYPE_CD | KEY_NAME | DATE_VAL | IDENTIFYING |
---|---|---|---|---|
1 | DATE | schedule.Date | 2019-01-01 | TRUE |
2 | DATE | schedule.Date | 2019-01-01 | TRUE |
3 | DATE | schedule.Date | 2019-01-02 | TRUE |
BATCH_JOB_EXECUTION:
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2019-01-01 21:00 | 2017-01-01 21:30 | FAILED |
2 | 1 | 2019-01-02 21:00 | 2017-01-02 21:30 | COMPLETED |
3 | 2 | 2019-01-02 21:31 | 2017-01-02 22:29 | COMPLETED |
从数据上看好似JobInstance
是一个接一个顺序执行的,可是对于Spring Batch并无进行任何控制。不一样的JobInstance
颇有多是同时在运行(相同的JobInstance
同时运行会抛出JobExecutionAlreadyRunningException
异常)。
Step
是批处理重复运行的最小单元,它按照顺序定义了一次执行的必要过程。所以每一个Job
能够视做由一个或多个多个Step
组成。一个Step
包含了全部全部进行批处理的必要信息,这些信息的内容是由开发人员决定的并无统一的标准。一个Step
能够很简单,也能够很复杂。他能够是复杂业务的组合,也有可能仅仅用于迁移数据。与JobExecution
的概念相似,Step
也有特定的StepExecution
,关系结构以下:
StepExecution
表示单次执行Step的容器,每次Step
执行时都会有一个新的StepExecution
被建立。与JobExecution
不一样的是,当某个Step
执行失败后默认并不会从新执行。StepExecution
包含如下属性:
属性 | 说明 |
---|---|
status | 状态类名为BatchStatus ,它指示了执行的状态。在执行的过程当中状态为BatchStatus#STARTED ,失败:BatchStatus#FAILED ,完成:BatchStatus#COMPLETED |
startTime | java.util.Date 对象,标记StepExecution 启动的系统时间,未启动数据为空 |
endTime | java.util.Date 对象,结束时间,不管是否成功都包含该数据,如未处理完为空 |
exitStatus | ExitStatus 类,记录运行结果。 |
createTime | java.util.Date ,JobExecution 的建立时间,某些使用execution已经建立可是并未开始运行。 |
lastUpdate | java.util.Date ,最后一次更新时间 |
executionContext | 批处理任务执行的全部用户数据 |
readCount | 成功读取数据的次数 |
wirteCount | 成功写入数据的次数 |
commitCount | 成功提交数据的次数 |
rollbackCount | 回归数据的次数,有业务代码触发 |
readSkipCount | 当读数据发生错误时跳过处理的次数 |
processSkipCount | 当处理过程发生错误,跳过处理的次数 |
filterCount | 被过滤规则拦截未处理的次数 |
writeSkipCount | 写数据失败,跳过处理的次数 |
前文已经屡次提到ExecutionContext
。能够简单的认为ExecutionContext
提供了一个Key/Value机制,在StepExecution
和JobExecution
对象的任何位置均可以获取到ExecutionContext
中的任何数据。最有价值的做用是记录数据的执行位置,以便发生重启时候从对应的位置继续执行:
executionContext.putLong(getKey(LINES_READ_COUNT), reader.getPosition())
好比在任务中有一个名为“loadData”的Step
,他的做用是从文件中读取数据写入到数据库,当第一次执行失败后,数据库中有以下数据:
BATCH_JOB_INSTANCE:
JOB_INST_ID | JOB_NAME |
---|---|
1 | EndOfDayJob |
BATCH_JOB_EXECUTION_PARAMS:
JOB_INST_ID | TYPE_CD | KEY_NAME | DATE_VAL |
---|---|---|---|
1 | DATE | schedule.Date | 2019-01-01 |
BATCH_JOB_EXECUTION:
JOB_EXEC_ID | JOB_INST_ID | START_TIME | END_TIME | STATUS |
---|---|---|---|---|
1 | 1 | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
BATCH_STEP_EXECUTION:
STEP_EXEC_ID | JOB_EXEC_ID | STEP_NAME | START_TIME | END_TIME | STATUS |
---|---|---|---|---|---|
1 | 1 | loadData | 2017-01-01 21:00 | 2017-01-01 21:30 | FAILED |
BATCH_STEP_EXECUTION_CONTEXT: |STEP_EXEC_ID|SHORT_CONTEXT| |---|---| |1|{piece.count=40321}|
在上面的例子中,Step
运行30分钟处理了40321个“pieces”,咱们姑且认为“pieces”表示行间的行数(实际就是每一个Step完成循环处理的个数)。这个值会在每一个commit
以前被更新记录在ExecutionContext
中(更新须要用到StepListener
后文会详细说明)。当咱们再次重启这个Job
时并记录在BATCH_STEP_EXECUTION_CONTEXT中的数据会加载到ExecutionContext
中,这样当咱们继续执行批处理任务时能够从上一次中断的位置继续处理。例以下面的代码在ItemReader
中检查上次执行的结果,并从中断的位置继续执行:
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(); } }
ExecutionContext
是根据JobInstance
进行管理的,所以只要是相同的实例都会具有相同的ExecutionContext(不管是否中止)。此外经过如下方法均可以得到一个ExecutionContext
:
ExecutionContext ecStep = stepExecution.getExecutionContext(); ExecutionContext ecJob = jobExecution.getExecutionContext();
可是这2个ExecutionContext
并不相同,前者是在一个Step
中每次Commit
数据之间共享,后者是在Step
与Step
之间共享。
JobRepository
是全部前面介绍的对象实例的持久化机制。他为JobLauncher
、Job
、Step
的实现提供了CRUD操做。当一个Job
第一次被启动时,一个JobExecution
会从数据源中获取到,同时在执行的过程当中StepExecution
、JobExecution
的实现都会记录到数据源中。使用@EnableBatchProcessing
注解后JobRepository
会进行自动化配置。
JobLauncher
为Job
的启动运行提供了一个边界的入口,在启动Job
的同时还能够定制JobParameters
:
public interface JobLauncher { public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException; }