[niubi-job——一个分布式的任务调度框架]----如何开发一个niubi-job的定时任务

kid2012 9年前

来自: 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。