作業調度哪種方式好,最終選了Quartz

        隨着雲平臺,大數據等的出現,用戶或潛在訪問者越來越想免費體驗產品,特別是想申請系統(比如開虛擬機,啓動docker等),可是系統硬件資源有限,那怎麼才能讓用戶實際操作雲平臺資源呢,那就是免費三天或七天的使用期(也有是一天的使用期),若過期了不花錢續費,系統就要自動清理雲環境申請的資源(好多雲廠商都是如此,比如某雲,免費試用到期了,會發郵件t提醒續費),這時就用到了作業調度的功能,這就要用定時器(它可以當做定時的任務,到期會執行,就像鬧鐘),java生態中有原生的定時器庫,也有第三方的比如Quartz,

下面我就用例子說明下如何使用

1.  java的原生定時器組件Timer和TimerTask

Timer是一種定時器工具,用來在一個後臺線程計劃執行指定任務,它可以計劃執行一個任務一次或反覆多次。

Timer類中常見方法

public class Timer {

    /**
    * task queue
    */
    private final TaskQueue queue = new TaskQueue();
    /**
     * The timer thread.
     */
    private final TimerThread thread = new TimerThread(queue);

    public Timer( );
    public Timer(boolean isDaemon);
    public Timer(String name);
    public Timer(String name, boolean isDaemon);

    //在指定延遲後執行指定的任務
    public void schedule(TimerTask task, long delay);
    //在指定的時間執行指定的任務
    public void schedule(TimerTask task, Date time);
    //指定的任務從指定的延遲後開始進行重複的固定延遲執行
    public void schedule(TimerTask task, long delay, long period);
    //指定的任務在指定的時間開始進行重複的固定延遲執行
    public void schedule(TimerTask task, Date firstTime, long period);

    //指定的任務在指定的延遲後開始進行重複的固定速率執行
    public void scheduleAtFixedRate(TimerTask task, long delay, long period);
    //指定的任務在指定的時間開始進行重複的固定速率執行
    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);

    //終止計時器,丟棄所有當前已安排的任務
    public void cancel( );
    //從計時器的任務隊列中移除所有已取消的任務
    public int purge( );

}

而TimerTask是一個抽象類,實現了Runnable接口,所以也就具備多線程的能力,它的子類代表一個可以被Timer計劃的任務。

下面是一個簡單的例子,編寫任務代碼

/**
 * 
 * @author dgm
 * @describe "測試打印定時器"
 * @date 2017年4月10日
 */
public class PrintTimerTask extends TimerTask {

	private String name;
	
	public PrintTimerTask(String name) {
		super();
		this.name = name;
	}

	@Override
	public void run() {
       if (System.currentTimeMillis( ) - scheduledExecutionTime( ) > 5000) {
            return;
        }

        System.out.println("線程:"+ name +"***** 在 執行。。"); 
	}
}

 編寫測試代碼

/**
 * 
 * @author dgm
 * @describe "測試定時器"
 * @date 2017年4月10日
 */
public class TimeTaskTest {

	public static void main(String[] args) {
		Timer timer = new Timer();
		//設置3秒後啓動任務
		timer.schedule(new PrintTimerTask("name-0"), 3000);
		PrintTimerTask secondTask = new PrintTimerTask("name-1");
		// 1秒後啓動任務,以後每隔3秒執行一次線程
		timer.schedule(secondTask, 1000, 3000);
		Date date = new Date();
		// 以date爲參數,指定某個時間點執行線程
		timer.schedule(new PrintTimerTask("name-3"), new Date(
				date.getTime() + 5000));
	}
}

 測試結果如圖:

注意此方法:

雖然jdk原生能實現需要的定時清理功能,查閱了資料後發現還有更好的方法,接着往下看 

2.  ScheduledExecutorService(一個接口)

ScheduledExecutorService,是基於線程池設計的定時任務類,每個調度任務都會分配到線程池中的一個線程去執行,也就是說,任務是併發執行,互不影響。

一個小例子如下

編寫要執行的線程任務

public class PrintScheduledExecutor implements Runnable {

	private String jobName;

	public PrintScheduledExecutor(String jobName) {
		this.jobName = jobName;
	}

	@Override
	public void run() {

		System.out.println(jobName + " 正在運行中!!!");
	}
}

測試定時任務執行

 	public static void main(String[] args) {
       
       ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

		long initialDelay = 1;
		long period = 1;
		service.scheduleAtFixedRate(new PrintScheduledExecutor("job1"),
				initialDelay, period, TimeUnit.SECONDS);

		service.scheduleWithFixedDelay(new PrintScheduledExecutor("job2"),
				initialDelay, period, TimeUnit.SECONDS);
     }

 注意scheduleAtFixedRate和scheduleWithFixedDelay:

 這個scheduledexecutorservice似乎是不錯,我又想有沒有更好的調度框架,這時又找到了Quartz(也是我現實中用到的定時調度庫)

3.  Quartz要登場了,強大無比

       簡單介紹下,Quarzt是一個項目中定時執行任務的開源項目,Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程序相結合也可以單獨使用。它的結構圖如圖示:

job任務類:表示的任務是什麼(比如自定義的任務種類比如清理試用賬號申請的資源),需要定時執行代碼的類。

JobDetail:配置任務類的細節,即注入任務類和指定任務類的方法,是一個可執行的工作,它本身可能是有狀態的。

trigger:即觸發器,代表一個調度參數的配置,配置調用的時間,表示何時觸發job任務。

調度工廠(scheduler):是一個計劃調度器容器,容器裏面可以盛放衆多的JobDetail和trigger,當容器啓動後,裏面的每個JobDetail都會根據trigger按部就班自動去執行。

  下面以案例說下如何使用(由於我不是單獨使用的,是和spring整合使用)

創建自定義job類(僞代碼)

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.context.ApplicationContext;


/**
 * 銷燬試用賬號老師的docker集羣
 * Created by dongguangming on 2017/4/14.
 */
public class DeTryTeacherAppJob implements Job 
{
    public static Logger logger = Logger.getLogger(DeTryTeacherAppJob.class);
    
    @Override
    public void execute(JobExecutionContext jobCtx) throws JobExecutionException 
    {
    logger.info("[" + currentTime + "]判定失效試用賬號任務開始");
        Map datamap = jobCtx.getJobDetail().getJobDataMap();
        ApplicationContext ctx = (ApplicationContext) datamap.get("applicationContext");
        UserService userService = (UserService) ctx.getBean("userService");
        AppService appService = (AppService) ctx.getBean("appService");
       // 執行業務操作
        ...............
    }
}

然後再spring配置任務類的bean和配置觸發器(時間)

     <!--銷燬試用賬號的docker集羣job -->
    <bean  name="deTryTeacherAppJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"
    	p:jobClass="com.cstor.docker.job.DeTryTeacherAppJob"
    	p:applicationContextJobDataKey="applicationContext">
    </bean>
<!-- 參考http://www.cnblogs.com/ypf1989/p/5552426.html http://www.cnblogs.com/30go/p/5761917.html -->
    <bean id="deTryTeacherAppJob" class="org.springframework.scheduling.quartz.CronTriggerBean"
          p:jobDetail-ref="deTryTeacherAppJobDetail"
          p:cronExpression="0 0/3 * * * ?"
        />

最後配置調度工廠並且注入配置好的觸發器

 <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
            <property name="triggers">
                <list>
                    <ref bean="deTryTeacherAppJob">
                </list>
            </property>
    </bean>

 現在可以啓動測驗了,測試時可以調整觸發時間(比如每分鐘),下面是con表達式樣板

"0 0 12 * * ?" 每天中午12點觸發
"0 15 10 ? * *" 每天上午10:15觸發
"0 15 10 * * ?" 每天上午10:15觸發
"0 15 10 * * ? *" 每天上午10:15觸發
"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發
"0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鐘觸發
"0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鐘觸發
"0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
"0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鐘觸發
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發
"0 15 10 ? * MON-FRI" 週一至週五的上午10:15觸發
"0 15 10 15 * ?" 每月15日上午10:15觸發
"0 15 10 L * ?" 每月最後一日的上午10:15觸發
"0 15 10 ? * 6L" 每月的最後一個星期五上午10:15觸發 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最後一個星期五上午10:15觸發
"0 15 10 ? * 6#3" 每月的第三個星期五上午10:15觸發

 

 

小結:

1.  Timer在執行定時任務時只創建一個線程。

2.  Timer線程並不捕獲異常,TimerTask拋出的未檢查的異常會終止timer線程,導致剩下的任務沒法執行(這很不好)。

3.  ScheduledExecutorService繼承於ExecutorService,ScheduledThreadPool內部是線程池,支持多個任務併發執行.

4.  Quartz很靈活,和spring集成的很好。

d...........

 

Timer&TimerTask→→→→→ScheduledExecutorService(體系複雜)→→→→→Quartz


參考:

0.  Java Timer vs ExecutorService? 

1  https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html

2.  幾種任務調度的 Java 實現方法與比較: https://www.ibm.com/developerworks/cn/java/j-lo-taskschedule/

3.  cron表達式生成工具: http://cron.qqe2.com/

4.  http://www.quartz-scheduler.org/documentation/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章