Spring Scheduler定時任務 + Quartz

gary-liu發表於2017-03-11

定時任務幾種實現方式

  1. Java自帶的java.util.Timer類,這個類允許你排程一個java.util.TimerTask任務,沒怎麼用過就不說了。
  2. Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。
  3. java的執行緒池類ScheduledExecutorService也可以實現一些簡單的定時任務,週期性任務。
  4. Quartz是一個功能比較強大的的排程器,可以讓你的程式在指定時間執行,也可以按照某一個頻度執行,可以方便的分散式部署、便捷的監控和管理任務,適合任務很多的情況。

Spring Scheduler註解方式實現

程式碼還是挺少的

@Component
public class SchedulerPractice{
    // @Scheduled(fixedDelay=60000)
    @Scheduled(cron = "0 0/1 * * * ?")
 public void execute() {
            logger.info("every one minute------");
 }
}

配置檔案

<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:task="http://www.springframework.org/schema/task" 
                       http://www.springframework.org/schema/task  
                       http://www.springframework.org/schema/task/spring-task-3.0.xsd
                     >
 <context:annotation-config />  
    <!—spring掃描註解的配置   -->  
    <context:component-scan base-package="com.liu” />  
<task:annotation-driven />

註解@Scheduled 中有三個方法,用來對執行規則的配置:

cron:指定cron表示式,文章最後有些配置示例。

fixedDelay:即表示從上一個任務完成開始到下一個任務開始的間隔,單位是毫秒。

fixedRate:即從上一個任務開始到下一個任務開始的間隔,單位是毫秒。

執行時間可配置化

將cron表示式配置在java的properties檔案或者環境變數中,是配置更靈活

xml配置方式


<task:scheduler id="myScheduler"/>

<task:scheduled-tasks scheduler="myScheduler" pool-size="2" />
    <task:scheduled ref=“testScheduler” method="execute()” cron="${cron_expression}"/>
</task:scheduled-tasks>

pool-size=”2” 有多個任務可以配置以執行緒池執行

註解使用方式

@Scheduled(cron = "${cron_expression}")

分散式多例項執行

scheduler與web配置在一起,在高可用的情況下,如果有多個web容器例項,scheduler會在多個例項上同時執行。

解決辦法:

  1. 使用寫死伺服器Host的方式執行task,存在單點風險,負載均衡手動完成。(或者一臺程式碼中配置任務,其他不配置任務)

  2. 在task的基類加入一些邏輯,當開始執行時,將狀態(執行機器的IP、時間等)寫入資料庫、快取(redis)或者zk,執行結束時重置狀態。其它例項看到有這樣的資料,就直接返回。帶來的問題是:
    一定要保證結束執行後將狀態重置,否則下一個執行週期,所有的task都會返回的。
    因為讀寫狀態並非原子操作,偶爾也會發生task同時執行的事。

  3. 使用zk分散式鎖,比如在任務執行方法上自定義註解,在註解中配置鎖在zk的路徑,在該註解上自定義個攔截器,在攔截器中獲取zk鎖。

Java執行緒池ScheduledExecutorService

示例程式碼如下

public void cronThread(){
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);

            scheduledThreadPool.scheduleWithFixedDelay(new ThreadPractice(), 0, 3, TimeUnit.SECONDS);

    }

new ThreadPractice()是一個實現了Runnable的類。
ScheduledExecutorService 類有兩個方法

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit)

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit)

這兩個方法和@scheduled 註解中的fixedDelay和fixedRate類似。

Quartz

Quartz框架是一個全功能、開源的任務排程服務,可以整合幾乎任何的java應用程式—從小的微控制器系統到大型的電子商務系統。可以方便的分散式部署、便捷的監控和管理任務,Quartz可以執行上千上萬的任務排程。

核心概念

Quartz核心的概念:scheduler任務排程、Job任務、Trigger觸發器、JobDetail任務細節

  • Job任務:其實Job是介面,其中只有一個execute方法, 只要實現此介面,實現execute方法即可。

  • JobDetail:任務細節,Quartz執行Job時,需要新建個Job例項,但是不能直接操作Job類,所以通過JobDetail來獲取Job的名稱、描述資訊。

  • Trigger觸發器:執行任務的規則;比如每天,每小時等。觸發器有SimpleTrigger和CronTrigger,這個觸發器實現了Trigger介面。對於複雜的時間表示式來說,比如每個月15日上午幾點幾分,使用CronTrigger,對於簡單的時間來說,比如每天執行幾次,使用SimpleTrigger。

  • scheduler任務排程:是最核心的概念,需要把JobDetail和Trigger註冊到scheduler中,才可以執行。

quartz單機示例

使用的quartz的jar的版本是:2.2.1 ,低版本的核心類可能有些不同。

job類

public class MyJob implements Job {  

    @Override  
    //把要執行的操作,寫在execute方法中  
    public void execute(JobExecutionContext arg0) throws JobExecutionException {  
        System.out.println(“test Quartz"+new Date());  
    }  
}

測試程式碼

@Test
    public void startJobTest() {

        SchedulerFactory schedulerfactory = new StdSchedulerFactory();
        Scheduler scheduler = null;
        try {
            // 通過schedulerFactory獲取一個排程器
            scheduler = schedulerfactory.getScheduler();

            // 建立jobDetail例項,繫結Job實現類
            // 指明job的名稱,所在組的名稱,以及繫結job類
            JobDetail job = JobBuilder.newJob(ImageTableMonitorJob.class).withIdentity("job1", "jgroup1").build();

            // 定義排程觸發規則
            // 使用simpleTrigger規則
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup")
                    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(1).withRepeatCount(8)).startNow().build();
            // 使用cornTrigger規則 每天10點42分
            // Trigger trigger= TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup")
            // .withSchedule(CronScheduleBuilder.cronSchedule("0 42 10 * * ? *"))
            // .startNow().build();

            // 把作業和觸發器註冊到任務排程中
            scheduler.scheduleJob(job, trigger);

            // 啟動排程
            scheduler.start();

        } catch (Exception e) {
            // e.printStackTrace();
        }
    }

spring提供了對quartz的整合,可以通過 org.springframework.scheduling.quartz.SchedulerFactoryBean 注入scheduler排程器,並且對排程器做些配置,比如使用執行緒池,並配置執行緒數量等。配置示例如下。

<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" >
        <property name="taskExecutor" ref="taskExecutor" />
        <property name="autoStartup" value="false"/>
    </bean>

    <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="2" />
        <property name="maxPoolSize" value="512" />
    </bean>

Quartz分散式原理

Quartz的叢集部署方案在架構上是分散式的,沒有負責集中管理的節點,而是利用資料庫鎖的方式來實現叢集環境下進行併發控制。分散式部署時需要保證各個節點的系統時間一致。沒弄過,就不細說了。

Quartz資料庫核心表QRTZ_LOCKS中有5條記錄CALENDAR_ACCESS,JOB_ACCESS,MISFIRE_ACCESS,STATE_ACCESS,TRIGGER_ACCESS 代表5把鎖,分別用於實現多個Quartz Node對Job、Trigger、Calendar訪問的同步控制。

cron表示式

欄位   允許值   允許的特殊字元
秒    0-59    , - * /
分    0-59    , - * /
小時    0-23    , - * /
日期    1-31    , - * ? / L W C
月份    1-12 或者 JAN-DEC    , - * /
星期    1-7 或者 SUN-SAT    , - * ? / L C #
年(可選)    留空, 1970-2099    , - * / 

- 區間  
* 萬用字元  
? 你不想設定那個欄位

下面列出一些例項

CRON表示式    含義 
"0 0 12 * * ?"    每天中午十二點觸發 
"0 15 10 ? * *"    每天早上1015觸發 
"0 15 10 * * ?"    每天早上1015觸發 
"0 15 10 * * ? *"    每天早上1015觸發 
"0 15 10 * * ? 2005"    2005年的每天早上1015觸發 
"0 * 14 * * ?"    每天從下午2點開始到259分每分鐘一次觸發 
"0 0/5 14 * * ?"    每天從下午2點開始到255分結束每5分鐘一次觸發 
"0 0/5 14,18 * * ?"    每天的下午2點至2556點至655分兩個時間段內每5分鐘一次觸發 
"0 0-5 14 * * ?"    每天14:0014:05每分鐘一次觸發 
"0 10,44 14 ? 3 WED"    三月的每週三的14101444觸發 
"0 15 10 ? * MON-FRI"    每個週一、週二、週三、週四、週五的1015觸發 

參考資料

Java任務排程框架Quartz教程例項
Spring Scheduler的使用與坑
Quartz應用與叢集原理分析

相關文章