[niubi-job——一个分布式的任务调度框架]----如何开发一个niubi-job的定时任务
来自: http://www.cnblogs.com/zuoxiaolong/p/niubi-job-2.html
引言
上篇文章LZ主要讲解了niubi-job如何安装,如果看过上一篇文章的话,大家应该知道,niubi-job执行的任务是需要用户自己上传jar包的。
那么问题来了,这个jar包如何产生?有没有要求?
本文就是来解决这个问题的,虽然LZ的github上面有例子,但是终究还是LZ自己解释一下会让大家更清晰一些。废话不多说,接下来咱们就来看看如何开发一个定时任务,并且可以运行在niubi-job的容器中。
概述
首先,LZ在设计的时候,主要将任务分成两大类:一类是运行在spring容器当中的任务,一类则是不运行在spring容器当中的任务。
什么叫运行在spring容器当中?
很简单,就是你的任务类引用了spring提供的bean,比如XXXService,或者是XXXXMapper,亦或是XXXXDao,又或者是其它。那么相反的,如果你的类可以独立运行,而不需要spring容器的运行环境,则被LZ统一看作是普通的任务。
PS:本文所有代码都取自 niubi-job-examples ,在阅读本文的时候,大家可以参考一下。
非spring环境的任务
以下这就是一个典型的非spring环境的niubi-job任务,取自niubi-job-example-common。
package com.zuoxiaolong.niubi.job.example.common.job; import com.zuoxiaolong.niubi.job.core.helper.LoggerHelper; import com.zuoxiaolong.niubi.job.scanner.annotation.Schedule; /** * @author Xiaolong Zuo * @since 16/1/18 22:25 */ public class Job1 { @Schedule(cron = "0/15 * * * * ?") public void job1Test() { LoggerHelper.info("[job1] is running......."); } }
niubi-job依靠@Schedule识别任务,因此如果你想让一个方法在niubi-job当中可以发布,则必须给该方法加上@Schedule注解。另外,cron这个属性并不是必须的,因为niubi-job是在控制台发布的时候,取你当时输入的cron为准,代码当中的cron属性会被忽略。
LZ这里之所以给该方法加上注解,是为了可以进行本地测试。这个特性很有用,你在开发的时候可能希望先在本地测试一下,然后才提交到niubi-job集群上去。同时,一般情况下,这也是必须的,通过本地测试是提交代码的前提。
这个时候你就可以给注解加上cron属性,然后利用以下这个简单的类,就可以在本地启动定时器了。
package com.zuoxiaolong.niubi.job.example.common; import com.zuoxiaolong.niubi.job.scheduler.node.Node; import com.zuoxiaolong.niubi.job.scheduler.node.SimpleLocalJobNode; /** * @author Xiaolong Zuo * @since 1/22/2016 14:13 */ public class Test { public static void main(String[] args) { Node node = new SimpleLocalJobNode("com.zuoxiaolong"); node.join(); } }
SimpleLocalJobNode这个类只有一个参数,就是你要扫描的包,也就是你的job所在的包的范围。当Node实例建立好以后,只需要调用它的join方法,就会启动定时器,这个时候使用的cron才是你注解上写的表达式。
因此,我们的结论就是, 注解上写的cron表达式(也包括其它属性,如misfirePolicy)只在本地生效,当任务被当作jar包提交上去以后,Schedule注解的任何属性都将会被忽略 。这一点特性不管是非spring环境的任务还是spring环境的任务,都是同样的。
有的同学可能会说了,难道就这么简单吗?
当然不是。
下面就是重点了,也算是niubi-job对上传的jar唯一的一点要求。请看这个项目的pom文件。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>niubi-job-examples</artifactId> <groupId>com.zuoxiaolong</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>niubi-job-example-common</artifactId> <dependencies> <!-- 如果要本地测试必须引入该jar包 --> <dependency> <groupId>com.zuoxiaolong</groupId> <artifactId>niubi-job-scheduler</artifactId> <version>0.9.2</version> </dependency> </dependencies> <profiles> <profile> <!-- 进行打包时,必须启用release这个profile,否则任务将无法被正常加载 --> <id>release</id> <dependencyManagement> <dependencies> <dependency> <groupId>com.zuoxiaolong</groupId> <artifactId>niubi-job-scheduler</artifactId> <version>0.9.2</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>niubi-job-example-common</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.2</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project>
这是niubi-job-example-common这个项目的pom文件,它说明了这个项目的依赖。首先注释上写了,如果要使用上面的Test类进行本地测试,则必须引入niubi-job-scheduler这个jar包,而且scope需要是默认的compile。
但是在niubi-job的节点中,容器已经包含了niubi-job-scheduler的所有类(包括其依赖的jar包中的类),因此在打jar包时,必须将该项目的其它依赖打进去(也就是maven-shade-plugin插件的作用),但是要把niubi-job-scheduler给排除掉。
因此这个时候,我们就写一个profile,并且把niubi-job-scheduler的scope定为provided,我们只需要在打包时激活这个profile,就会打出一个符合niubi-job标准的任务jar包。也就是执行package时,执行以下命令。
mvn clean package -P release
其实大家会发现,上面所说的现象和开发web应用时,对于servlet-api这个jar包的处理非常相似。你在开发时,由于有时候需要用到servlet-api的类(比如实现一个filter时,你需要实现Filter这个类),因此你必须引入servlet-api的依赖。但是与niubi-job的情况相似,tomcat容器本身已经包含了servlet-api的类,所以你必须在打包时把servlet-api排除,否则就会出现非常奇葩的类转换异常。
比如xxx.Filter无法转换成xxx.Filter这种奇葩异常,又或者是一个类明明实现了Filter接口,但是提示却说这个类无法转换成Filter。这个时候,新人往往就蒙圈了,Filter怎么会转换不成它自己,或者是转换不成它实现了的接口呢?
这个原因跟tomcat的类加载机制有关系,niubi-job也采用了和tomcat几乎一模一样的类加载机制(有时间LZ会详细解释一下niubi-job当中的类加载机制,当然了,大家也可以自己去下载源码研究),因此大家可以把niubi-job-scheduler这个包当成servlet-api这个jar包,切记在打jar包时不要把它打进去(PS:但切记要把其它的依赖jar包打进去,使用上面的shade插件就可以做到)。
只要记住上面提到的限制,你开发出来的jar包就可以在niubi-job的容器中运行了。
spring环境的任务
同样的,咱们先来看一个spring环境的任务的例子。下面这些类取自niubi-job-example-spring。
这个类是一个非常普通的spring的bean。在实际开发中,它可能是任何一个在spring容器中初始化出来的bean。
package com.zuoxiaolong.niubi.job.example.spring.bean; import com.zuoxiaolong.niubi.job.core.helper.LoggerHelper; import org.springframework.stereotype.Service; /** * @author Xiaolong Zuo * @since 16/1/18 22:33 */ @Service public class OneService { public void someServiceMethod1() { LoggerHelper.info("[job1] invoke [serviceMethod1] successfully......"); } public void someServiceMethod2() { LoggerHelper.info("[job2] invoke [serviceMethod2] successfully......"); } }
接下来就是一个需要在spring容器中运行的任务,因为它引用了上面这个bean。
package com.zuoxiaolong.niubi.job.example.spring.job; import com.zuoxiaolong.niubi.job.example.spring.bean.OneService; import com.zuoxiaolong.niubi.job.scanner.annotation.Schedule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author Xiaolong Zuo * @since 16/1/16 15:30 */ @Component public class Job1 { @Autowired private OneService oneService; @Schedule(cron = "0/15 * * * * ?") public void test() { oneService.someServiceMethod1(); } }
可以看到,Job1这个类必须被spring容器初始化,否则的话,oneService这个属性将无法被自动注入。这就是所谓的需要在spring容器中运行的任务。
与上面非spring环境的任务相似,这里注解上的cron属性依旧是为了本地测试用的。spring环境的任务依旧可以在本地测试,只需要在你的spring配置文件里加上这样一行。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:job="http://www.zuoxiaolong.com/schema/niubi-job" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.zuoxiaolong.com/schema/niubi-job http://www.zuoxiaolong.com/schema/niubi-job/niubi-job-1.0.xsd"> <!-- Annotation Config --> <context:annotation-config/> <context:component-scan base-package="com.zuoxiaolong.niubi.job.example.spring"/> <!-- 加上这一行就可以在本地做测试了 --> <job:job-driven packagesToScan="com.zuoxiaolong.niubi.job.example.spring"/> </beans>
如上,加上那一行的配置以后,就可以用下面这个类在本地运行定时任务了。
package com.zuoxiaolong.niubi.job.example.spring; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * use to test jobs. * * @author Xiaolong Zuo * @since 1/22/2016 14:19 */ public class Test { public static void main(String[] args) { new ClassPathXmlApplicationContext("applicationContext.xml"); } }
你只需要初始化一下spring容器,niubi-job就会自动帮你启动定时任务,这个时候的cron依旧取的是你注解上写的表达式。
接下来我们来看看它的pom文件,与非spring环境有什么区别。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>niubi-job-examples</artifactId> <groupId>com.zuoxiaolong</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>niubi-job-example-spring</artifactId> <dependencies> <!-- 为了本地测试,依旧要引入该包 --> <dependency> <groupId>com.zuoxiaolong</groupId> <artifactId>niubi-job-scheduler</artifactId> <version>0.9.2</version> </dependency> <!-- spring环境与非spring环境的任务最不同的就是spring环境的任务需要多引入这个包 --> <!-- 并且该包需要一起打到jar包当中 --> <dependency> <groupId>com.zuoxiaolong</groupId> <artifactId>niubi-job-spring</artifactId> <version>0.9.2</version> </dependency> <!-- 这是spring的jar包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency> </dependencies> <profiles> <profile> <!-- 以下配置与非spring环境一模一样 --> <id>release</id> <dependencyManagement> <dependencies> <dependency> <groupId>com.zuoxiaolong</groupId> <artifactId>niubi-job-scheduler</artifactId> <version>0.9.2</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement> <build> <finalName>niubi-job-example-spring</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.2</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> </project>
可以看到,与上面非spring环境相比,最大的不同就是多引入了一个niubi-job-spring的依赖,并且该包需要一起打到你的jar包当中,因此在release的profile中,把niubi-job-scheduler的scope改成了provided,但是niubi-job-spring却没任何改变。
还需要特别的一点是,niubi-job会自动扫描classpath下是否存在applicationContext.xml文件,以此来判断是否要以spring环境运行该jar包。因此,如果你希望你的jar包运行在spring环境中,请务必在你的classpath下建立一个applicationContext.xml文件。
如果你原本的spring配置文件不叫applicationContext.xml,而你又不想改原本spring配置的名字,那么可以在classpath建立一个applicationContext.xml文件,并且将你原本的spring配置文件用import标签导入。
总结
接下来,总结一下niubi-job对上传的任务jar包的要求。
1、jar包必须包含自己本身的依赖,例如数据库驱动等。(使用maven的shade插件就可以将依赖一起打包,如果是其它构建工具,请自行查找方法,应该不难)
2、jar包中不能包含niubi-job-scheduler以及其依赖的jar包,也就是不能包含niubi-job-cluster解压后lib内的jar包。(比如log4j, gson等,具体的可以自行查看)
3、如果需要spring的运行环境,请额外引入niubi-job-spring,并且在classpath下建立一个包含了你的spring配置的applicationContext.xml文件。(如果你的spring配置文件原本就叫applicationContext.xml,那就不需要专门建立applicationContext.xml文件了)
4、如果需要在本地测试,则在开发时将niubi-job-scheduler这个jar的scope设成compile,并且给你任务方法上的Schedule注解加上cron属性。记得,在打成jar包时将niubi-job-scheduler的scope改成provided。
任务jar包中的日志
当你引入niubi-job-scheduler这个jar包的时候,你可以找到一个LoggerHelper的类,它里面包含了一些打印日志的方法。强烈建议,如果要在任务中打印日志的话,请使用该类。使用该类打印的日志,都将出现在niubi-job-cluster的logs文件夹的日志文件里,可以非常方便的查看,也便于后期与elasticsearch集成。
有关和elasticsearch集成的内容,后期LZ会补充上来。集成以后,你可以非常方便的查看任务运行日志。如果你的公司本身就有一套基于elasticsearch的日志查看系统,那就更加完美了。
结束语
niubi-job是LZ倾心打造的一个项目,LZ会出一系列文章来介绍它,包括如何使用以及它的一些设计思想和原理,有兴趣的同学可以关注一下。
如果你的项目刚好缺一个定时任务的调度框架,那么niubi-job应该是你不二的选择!
当然,如果你有兴趣参与进来,也可以在Github上面给LZ提交PR,LZ一定尽职尽责的进行review。