第四章节、配置以及运行一个任务(下) – spring batch

(申明:初尝翻译,未经校验,请勿转载)

4.3、配置一个“任务启动器”

最简单的“任务启动器(JobLauncher)”接口的实现是“SimpleJobLauncher”。它只依赖于一个“任务存储器(JobRepository)”,使得拥有任务执行的能力:

4.3-1

只要拥有了“任务执行器(JobExecution)”,它将调用“任务”的执行方法,最终将“任务执行器”返回给调用者:

image

当从一个调度器开始启动时,执行顺序是径直的,并且运作良好。但如果从一个网站请求开始启动,那么问题就来了。在这个场景中,运行的过程需要是异步的,才能使得“SimpleJobLauncher”立即返回一个结果给调用者。这是因为长时间挂起一个网站请求而去运行一个批量任务肯定是行不通的。如下图时序图所示:

image

通过配置一个“子任务执行器(TaskExecutor)”这样简单的操作,就能使得“SimpleJobLauncher”适用于这个场景:

image

任何一个“子任务执行器”接口的实现、都能被用作去控制任务的异步执行。

4.4、运行一个任务

在运行一个任务时,有两样东西是必不可少的:需要运行的“任务(Job)”和一个“任务启动器(JobLauncher)”。他俩既可以放在同一个上下文中,也可以放在不同的上下文里。比如,当从命令启动一个任务时,一个新的Java虚拟机(JVM)将被实例化用来服务每个任务,如此一来,每个任务便有了它自己的“任务启动器(JobLaucher)”。但如果通过Web容器,通过“页面请求(HttpRequest)”,通常只会存在一个“任务启动器(JobLauncher)”,并且被配置为异步启动模式,这样多个请求才能并行不悖。

4.4.1、从命令行启动任务

对于想要从一个企业级别调度器运行任务的用户来说,命令行是首选的。这是因为大多数调度器(Quartz是个例外,使用了NativeJob)直接以操作系统进程的形式运行,普遍的通过系统脚本开始启动。除此之外还有很多方式启动一个java进程,比如Perl,Ruby,甚至诸如ant或maven这样的“构建工具(build tools)”。但因为大多数人习惯于脚本,这个例子将主要关注这个。

4.4.1.1、命令行任务启动器(CommandLineJobRunner)

因为脚本启动一个任务必须通过java虚拟机,所以需要一个包含有“main”方法的类用来作为程序的入口。Spring Batch提供了一个实现恰能满足这个要求:“命令行任务启动器(CommandLineJobRunner)”。必须注意到这只是启动任务的一种方法,除此之外有大量其它的方式启动一个java进程,千万不要以为这是必由之路。“命令行任务启动器(CommandLineJobLauncher)”运行为四大步骤:

加载适当的应用上下文
解释命令行参数为“任务参数(JobParameter)
基于参数定位适当的任务
使用“任务启动器(JobLauncher)”提供启动任务时需要的应用上下文

所有这些步骤将只根据传入的参数执行直至完成。如下是必须的参数列表:

image

这些参数必须依照“路径(path)”为先、“名称(name)”随后的顺序传入。所有之后的参数都被认为是“任务参数”,而且格式必须为“名称=值”:

image

大多数时候你需要显示申明你打包在jar文件中的main方法所在的类,但为了简单起见,直接使用即可。例子中使用了同一个域下相同的“EndOfDay”。第一个参数是“endOfDayJob.xml”,指明了包含任务的spring“应用上下文(ApplicationContext)”。第二个参数“endOfDay”表明了任务名称。最后的参数,“schedule.date(date)=2007/05/05”将被转换为“任务参数(JobParameters)”。XML的配置例子如下:

image

这个例子过于简单了,因为通常在spring batch中启动一个任务有多得多的需要的地方,但用它来表示两个“命令行启动器(CommandLineJobRunner)”基本的两项配置还是不错的:“任务(Job)”以及“任务启动器(JobLauncher)”。

4.4.1.2、退出码

一般在通过命令行启动一个批量任务时,总是使用一个企业级的调度器。大多数的调度器是在后台执行、并且仅在进程级别。这意味着它们仅仅知道操作系统进程相关的信息,比如它们调用的shell脚本。在这种场景下,告知后台调度器某个任务是成功还是失败,只能通过任务执行后的返回码。所谓返回码就是指被执行的进程在最后返回给调度器的结果代码。最简单的情况下:0表示成功,1表示失败。不过通常的场景比这个要复杂得多:如果任务A返回4就开始任务B,如果返回5就开始任务C。这样类型的行为就可以再调度器级别进行配置,但重要的是当前使用的框架,比如spring batch,要提供这样一个途径、为某个特殊的任务返回多样的“退出码(Exit Code)”。在spring batch 中退出码被包装为“退出状态(ExitStatus)”,而这个将在第五章节详细讨论。既然是在讨论退出码,只需要记住一件重要的事情,那就是一个“退出状态(ExitStatus)”拥有一个被框架(或开发人员)设置的退出码属性,并且作为“任务执行过程(JobExecution)”的一部分返回给“任务启动器(JobLaucher)”。“命令行任务执行器(CommandLineJobRunner)”会通过接口“退出码映射器(ExitCodeMapper)”将字符串值转换为整型:

image

基本的“退出码映射器(ExitCodeMapper)”契约是这样的,给定一个字符串退出码,将返回对应的整型。在任务执行之后,默认的退出码映射器接口的实现是“SimpleJvmExitCodeMapper”,当任务完成时返回0,返回1表示一般性错误,返回2表示任务启动级别的错误、比如在提供的上下文中找不到指定的任务。如果需要更复杂的多余三种结果的返回码,那么就要提供一个自定义实现的“退出码映射器”。因为“命令行任务启动器”创建了应用上下文,为了防止“裹在一起”,任何需要被覆盖的值就需要自动装配了。这意味着如果自定义实现了一个“退出码映射器”并被“Bean工厂(BeanFactory)”找到,当上下文创建完成之后,它将被自动注入到启动器中。唯一需要做的就是将你自定义的“退出码映射器”申明为根级别的Bean接口的实现,同时确保在启动器装载的“应用上下文(ApplicationContext)”中,它是其中的一部分。

4.4.2、通过Web容器执行任务

通常情况下,离线的批量任务处理都是通过命令行启动的,正如上面所讨论的。但也有很多情况下,通过“页面请求(HttpRequest)”启动一个任务是更好的选择。很多这样的用例,比如报表、ad-hoc任务执行以及web应用支持。因为批量任务从定义上就决定了是一个长时间处理的过程,所以最重要的地方是一定要确保以异步的形式启动任务:

image

图中的“控制器(controller)”是spring MVC controller。更多spring MVC相关的内容请参考:http://static.springframework.org/spring/docs/2.5.x/reference/mvc.html。通过配置好的“任务启动器(JobLauncher)”,控制器可以异步地启动一个任务,同时立即返回一个“任务执行过程(JobExecution)”。虽然任务还在执行,但是非阻塞模式允许控制器立即返回,这对于处理“页面请求”来说至关重要。举个例子:

image

4.5、高级的元数据用法

至此,我们讨论了“任务启动器(JobLauncher)”和“任务存储器(JobRepository)”这两个接口。它们一起实现了如何去启动一个任务,如何对批量的域对象执行基本的“增删查改(CRUD)”操作:

image

“任务启动器(JobLauncher)”使用“任务存储器(JobRepository)”去创建新的“任务执行器(JobExecution)”,并且运行它们。在任务执行的过程之中,“任务(Job)”和“步骤(Step)”的实现实例会在后面使用同一个“任务存储器(JobRepository)”做简单的更新操作。这些基本的操作只能满足简单的场景,如果在一个任务数以百计、并且流程需求复杂的大的批量任务环境里,更需要进一步的访问元数据了:

image

接下来我们将讨论“任务浏览器(JobExplorer)”和“任务操作(JobOperator)”接口,它们将在一定程度上满足对元数据的查询和控制。

4.5.1、查询存储器

在使用到任何高级特性之前,最基本的需求莫过于能够去查询当前存储器中已有的执行器。“任务浏览器(JobExplorer)”接口提供了这样的方法:

image

如同上面方法的签名所示,“任务浏览器(JObExplorer)”是“任务存储器(JobRepository)”的只读版本,像后者一样,它可以通过工厂bean简单的配置生成:

image

在本章的早些部分,我们有提到“任务存储器(JobRepository)”的表前缀可以更改,使得可以支持不同的版本或架构。因为“任务浏览器(JobExplorer)”使用相同的表,所以可以配置表前缀就极其的重要了:

image

4.5.2、任务注册器

所谓的“任务注册器(JobRegistry)”不是必须的(它的父接口是“任务定位器JobLocator”),但如果你想追踪哪些任务在当前的上下文中是有效的,那么它就很有用。同时对于在一个应用上下文中集中的收集在别处创建的任务也很有用(比如在下级上下文中)。自定义实现“任务注册器(JobRegistry)”也可以用来更改任务名称或其他已注册的属性。框架只提供了一种实现,基于简单的映射表(map):名称,与其对应的任务实例。可以简单的这样配置:

image

有两种自动构成“任务注册器(JobRegistry)”的方法:使用bean发送处理器,以及使用“注册生命周期管理容器”。接下来讨论这两种方法。

4.5.2.1、任务处理器的Bean发送处理器(JobRegistryBeanPostProcessor)

这是一个bean发送处理器,可以用来注册所有已创建的任务:

image

虽然确切的说例子里给“发送处理器”分配了一个id是不必要的,但却能通过在下级上下文指定它(自身定义为上级bean),就能将所有已创建的任务自动进行注册。

4.5.2.2、自动任务注册器

这是一个生命周期管理容器,用来创建下级上下文并且注册这些上下文中创建的所有任务。这样做的优点就是,它们的依赖项可以拥有“自然一点”的名字,但依旧必须在当前注册器中全局唯一。举个例子,你可以创建一大堆XML配置文件,每一个里面都只含有一个任务,但都使用同一个名字定义了不同的“元素读取器(ItemReader)”,比如就叫“reader”。如果这些配置文件都被引入了同一个上下文,那么读取器的定义将会冲突并覆盖其它同名的定义,但如果通过自动注册器,这样的情况就不会发生。这使得集成各个分离的应用模块到某一个任务变的简单了。

image

注册器含有两个托管属性,一个是“应用上下文工厂(ApplicationContextFactory)”(将从恰当的工厂bean创建),另一个就是“任务装载器(JobLoader)”。任务装载器负责管理下级上下文的生命周期以及注册任务到任务存储器中。

“应用上下文工厂(ApplicationContextFactory)”负责创建下级上下文,最普遍的用法就是上面看到的ClassPathXmlApplicationContextFactory。这个工厂类的一个特性就是、默认地将上一级上下文中的若干配置拷贝到下一级。如此说来,如果下一级上下文定义的PropertyPlaceholderConfigurer或AOP配置与上一级相同,那么就不需要重复定义了。

如果强烈要求的话,AutomaticJobRegistrar可以和JobRegistryBeanPostProcessor联合起来用(就好像使用DefaultJobLoader一样)。比如在上一级主上下文中定义的任务,同样要用于下一级时。

4.5.3、任务操作类(JobOperator)

如前所述,“任务存储器(JobRepository)”提供了针对元数据的“增删查改”操作,而“任务浏览器(JobExplorer)”则提供了若干只读操作。通过联合使用诸多的批量操作类,可以完成相当多的任务控制功能,比如停止、重启或对任务进行汇总。Spring Batch正是定义了“任务操作类(JobOperator)”这样的接口、提供这样的功能:

image

上图中展示的操作重现了很多了其它接口提供的功能,比如“任务启动类(JobLaucher)”,“任务存储器(JobRepository)”,“任务浏览器(JobExplorer)”,还有“任务注册器(JobRegistry)”。因为这个原因,所提供的“任务操作接口(JobOperator)”的实现SimpleJobOperator依赖项有很多:

image

注意:

如果你在任务存储器中设置了表前缀,那么千万不要忘记了在任务浏览器中也做同样设置。

4.5.4、任务参数增量器(JobParametersIncrementer)

大多数“任务操作类(JobOperator)”的方法都是“自说明”的,通过接口的javadoc,你可以获得更详细的说明。但方法“startNextInstance”却有些无所是处。这个方法提供任务的一个新实例。如果存在若干“任务执行过程(JobExecution)”,同时该任务需要从头重新启动,那么这时候这个方法就相当有用了。不像“任务启动器(JobLauncher)”,启动新的任务会导致需要一个新的参数实例、如果参数中有任何的不同的话,startNextInstance方式将使用当前的“任务参数增量器(JobParametersIncrementer)”绑定到这个任务,并且强制其生成新的实例:

image

“任务参数增量器”的协议是这样的,当给定一个“任务参数”对象,它将返回“下一个”“任务参数”对象,同时填充任何可能需要的值。这个策略非常有用,因为框架无需知晓变成“下一个”任务参数实例,它做了哪些更改。比方说,如果任务参数中只包含一个日期参数,那么当创建下一个实例时,这个值就应该是不是该自增一天?或者一周(如果任务是以周围单位运行的话)?任何包含数值类参数的任务,如果需要对其进行区分,都涉及这个问题,如下:

image

在这个例子里,键值“run.id”用以区分各个任务实例。如果当前的任务参数为空(null),它将被视为该任务从未运行过,并同时为其初始化,然后返回。反之,非空的时候自增一个数值,再返回。自增的数值可以通过任务的“incrementer”属性进行设置:

image

4.5.5、停止任务

“任务操作类(JobOperator)”最常见的作用莫过于停止某个任务:

image

关闭不是立即发生的,因为根本不可能将一个任务立刻停掉,尤其是当任务进行到开发人员自己的代码段时,框架在此刻是无能为力的,比如某个业务逻辑处理。而一旦控制权还给了框架,它会立刻设置当前“执行步骤(StepExecution)”为“BachStatus.STOPPED”,意为停止,然后保存,最后在完成前对“任务执行器(JobExecution)”做相同的操作。

4.5.6、取消任务

一个任务的执行过程当执行到失败(FAILED)之后,如果它是可重启的,它将会被重启。如果任务的执行过程状态是“取消(ABORTED)”,那么框架就不会重启它。“取消”状态也适用于执行步骤,使得它们可以被跳过,即便是在一个可重启的任务执行之中:如果任务执行过程中碰到在上一次执行失败后标记为“取消”的步骤,将会跳过该步骤直接到下一步(这是由任务流定义和执行步骤的退出码决定的)。

如果当前的系统进程死掉了(“kill -9”或系统错误),任务自然也不在运行,但“任务存储器(JobRepository)”是无法侦测到这个错误的,因为进程死掉之前没有任何人通知它。你必须手动的告诉它,你知道任务已经失败了还是说考虑放弃这个任务(设置它的状态为FAILED或ABORTED)-这是业务逻辑层的事情,无法做到自动决策。只有在不可重启的任务重才需要设置为失败状态,或者你知道重启后数据还是有效的。Spring Batch Admin中有一系列工具“任务服务(JobService)”,用以取消正在进行的执行的任务。

posted @ 2012-08-07 16:10  陛下  阅读(8687)  评论(0编辑  收藏  举报