[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.......");
}
}</pre>
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();
}
}</pre>
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 ; <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></pre>
這是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......");
}
}</pre>
接下來就是一個需要在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();
}
}</pre>
可以看到,Job1這個類必須被spring容器初始化,否則的話,oneService這個屬性將無法被自動注入。這就是所謂的需要在spring容器中運行的任務。
與上面非spring環境的任務相似,這里注解上的cron屬性依舊是為了本地測試用的。spring環境的任務依舊可以在本地測試,只需要在你的spring配置文件里加上這樣一行。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="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 ;<!-- 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></pre>
如上,加上那一行的配置以后,就可以用下面這個類在本地運行定時任務了。
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");
}
}</pre>
你只需要初始化一下spring容器,niubi-job就會自動幫你啟動定時任務,這個時候的cron依舊取的是你注解上寫的表達式。
接下來我們來看看它的pom文件,與非spring環境有什么區別。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0 ; <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></pre>
可以看到,與上面非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。
</div>