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

引言

  

  上篇文章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。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页