您的位置:首页 > 其它

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

2016-01-30 12:55 836 查看

引言

  

  上篇文章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包时将[b]niubi-job-scheduler的scope改成provided。[/b]

  

任务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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: