使用Quartz實現定時任務

一:Quertz的用途

  

  Quertz是一個開源的做業任務調度框架,他能夠完成像JavaScript定時器類式的功能,其實Java中Timer也可實現部分功能,但相比Quertz仍是略遜一籌,本人此次須要解決的就是按期統計消費記錄的功能。你還能夠用他完成按期執行各種操做的功能。好比html

    • 想每個月25號,信用卡自動還款
    • 想每一年4月1日本身給當年暗戀女神發一封匿名賀卡
    • 想每隔1小時,備份一下本身的學習筆記到雲盤

這些問題總結起來就是:java

在某一個有規律的時間點幹某件事。而且時間的觸發的條件能夠很是複雜(好比每個月最後一個工做日的17:50),複雜到須要一個專門的框架來幹這個事。spring

Quartz就是來幹這樣的事,你給它一個觸發條件的定義,它負責到了時間點,觸發相應的Job起來幹活。數據庫

二:使用方法(主要是Spring整合使用)

採用Spring整合Quartz使用代碼方式或者xml方式均可以,我這裏也提供兩種方式,名稱相同適合對比學習。設計模式

代碼方式

  1:引入配置,pom.xml文件引入下列兩個路徑(非Maven可自行配置)api

        <!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.3.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.0.RELEASE</version>
        </dependency>
Pom.xml

  2:建立【com.xqc.campusshop.config.quartz】包進行相關配置服務器

(我知道你們不喜歡看源碼,可是我仍是得說看源碼效果好)源碼中併發

productSellDailyService爲按期統計消費記錄Service接口  框架

dailyCalculate 爲ProductSellDailyService接口中執行按期統計的的方法,ssh

triggerFactory.setCronExpression("? 0 0 * * ? *");爲定時的時間,可訪問在線cron表達式生成器生成相應時間 http://cron.qqe2.com/

 1 package com.xqc.campusshop.config.quartz;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.context.annotation.Bean;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
 7 import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
 8 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 9 
10 import com.xqc.campusshop.service.ProductSellDailyService;
11 
12 @Configuration
13 public class QuartzConfiguration {
14     
15     //按期統計消費記錄Service接口
16     @Autowired
17     private ProductSellDailyService productSellDailyService;
18     
19     @Autowired
20     private MethodInvokingJobDetailFactoryBean jobDetailFactory;
21     
22     @Autowired
23     private CronTriggerFactoryBean productSellDailyTriggerFactory;
24     
25     /**
26      * 建立jobDetail並返回
27      * @return
28      */
29     @Bean(name="jobDetailFactory")
30     public MethodInvokingJobDetailFactoryBean crateJobDetail(){
31     
32         //new出jobDetailFactory對象,此工廠主要用來製做一個jobDetail,及製做一個任務
33         //因爲咱們所作的定時任務根本上講其實就是執行一個方法,因此這個工廠比較方便
34         MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
35         //設置jobDetail的名字
36         jobDetailFactoryBean.setName("product_sell_daily_job");
37         //設置jobDetail的組名
38         jobDetailFactoryBean.setGroup("job_product_sell_daily_group");
39         //對於相同的JobDetail,當指定多個Triggger時,極可能第一個job完成之前,第二個job就開始了
40         //指定設爲false,多個job則不會併發運行,第二個job不會再第一個job完成前開始
41         jobDetailFactoryBean.setConcurrent(false);
42         //指定運行任務的類
43         jobDetailFactoryBean.setTargetObject(productSellDailyService);
44         //指定運行任務的方法
45         jobDetailFactoryBean.setTargetMethod("dailyCalculate");
46         
47         return jobDetailFactoryBean;    
48     }
49     
50     /**
51      * 建立cronTriggerFactory並返回
52      * 
53      * @return
54      */
55     @Bean("productSellDailyTriggerFactory")
56     public CronTriggerFactoryBean createProductSellDailyTrigger(){
57         //建立TriggerFactory實例,用來建立trigger
58         CronTriggerFactoryBean triggerFactory = new CronTriggerFactoryBean();
59         //設置triggerFactory的名字
60         triggerFactory.setName("product_sell_daily_trigger");
61         //設置組名
62         triggerFactory.setGroup("job_product_sell_daily_group");
63         //綁定jobDetail
64         triggerFactory.setJobDetail(jobDetailFactory.getObject());
65         //設置cron表達式,請訪問:http://cron.qqe2.com/在線表達式生成器
66         triggerFactory.setCronExpression("? 0 0 * * ? *");
67         
68         return triggerFactory;
69         
70     }
71     /**
72      * 建立調度工廠並返回
73      * @return
74      */
75     @Bean("schedulerFactory")
76     public SchedulerFactoryBean createSchedulerFactory(){
77         
78         SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
79         schedulerFactory.setTriggers(productSellDailyTriggerFactory.getObject());
80         return schedulerFactory;
81         
82     }
83     
84 
85 }

 

3:業務方法我就不貼了,你們能夠打印一下測試一下便可(記得把cron表達式時間改小一點),譬如

 1 package com.xqc.campusshop.service.impl;
 2 
 3 import org.springframework.stereotype.Service;
 4 
 5 @Service
 6 public class ProductSellDailyServiceImpl implements ProductSellDailyService{
 7     
 8     @Override
 9     public void dailyCalculate() {
10             system.out.println("Quartz跑起來了!」);
11         
12     }    
13 }

使用配置文件方式

1:引入pom.xml(同上)

2:在Spring配置文件配置以下

<!-- 使用MethodInvokingJobDetailFactoryBean,任務類能夠不實現Job接口,經過targetMethod指定調用方法-->
    <bean id="productSellDailyService" class="com.xqc.campusshop.service"/>
    <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="group" value="job_product_sell_daily_group"/>
        <property name="name" value="product_sell_daily_job"/>
        <!--false表示等上一個任務執行完後再開啓新的任務-->
        <property name="concurrent" value="false"/>
        <property name="targetObject">
            <ref bean="productSellDailyService"/>
        </property>
        <property name="targetMethod">
            <value>dailyCalculate</value>
        </property>
    </bean>
    <!--  調度觸發器 -->
    <bean id="myTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="name" value="product_sell_daily_trigger"/>
        <property name="group" value="job_product_sell_daily_group"/>
        <property name="jobDetail">
            <ref bean="jobDetail" />
        </property>
        <property name="cronExpression">
            <value>? 0 0 * * ? *</value>
        </property>
    </bean>
    <!-- 調度工廠 -->
    <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="myTrigger"/>
            </list>
        </property>
    </bean>

3:編寫業務方法(同上)

三:相關知識

官網:http://www.quartz-scheduler.org/

API:http://www.quartz-scheduler.org/api/2.2.1/index.html

三個核心概念:

Scheduler:調度器。全部的調度都是由它控制。

Trigger: 定義觸發的條件。

 Job & JobDetail: JobDetail 定義的是任務數據,而真正的執行邏輯是在Job中,爲何設計成 JobDetail + Job,不直接使用Job?這是由於任務是有可能併發執行,若是Scheduler直接使用Job,就會存在對同一個Job實例併發訪問的問題。而JobDetail & Job 方式,sheduler每次執行,都會根據JobDetail建立一個新的Job實例,這樣就能夠規避併發訪問的問題。

  核心關係圖

 

Quartz體系結構

重要組成部分

Job

Job實例在Quartz中的生命週期

每次調度器執行Job時,它在調用execute方法前會建立一個新的Job實例

當調用完成後,關聯的Job對象實例會被釋放,釋放實例會被垃圾回收機制回收。

 

JobBuild

 

JonDetail

JobDetail爲Job實例提供了許多設置屬性,以及JobDataMap成員變量屬性,它用來存儲特定Job實例信息,能夠理解爲Job攜帶的內容。調度器須要藉助JobDetail對象來添加Job實例

JobDetail和Trigger都有namegroup

name是它們在這個sheduler裏面的惟一標識。若是咱們要更新一個JobDetail定義,只須要設置一個name相同的JobDetail實例便可。

group是一個組織單元,sheduler會提供一些對整組操做的API,好比 scheduler.resumeJobs()。

JobExecutionContext

當Scheduler調用一個Job,就會將JobExecutionContext傳遞給Job的execute()方法

Job能經過JobExecutionContext對象訪問到Quartz運行時候的環境以及Job自己的明細數據

JobDataMap

在進行任務調度時JobDataMap存儲,在JobExecutionContext中,很是方便獲取

JobDataMap能夠用來裝載任何可序列化的數據對象,當Job實例對象被執行時這些參數對象會傳遞給他

JobDataMap實現了JDK的Map接口,而且添加了一些很是方便的方法用來存取基本的數據類型

獲取JobDataMap的兩種方式

從Map中直接獲取

Job實現類中添加Setter方法對應JobDataMap的鍵值(Quartz框架默認的JobFactory實現類在初始化Job實例對象時就會自動調用這些setter方法

Trigger

startTimeendTime指定的Trigger會被觸發的時間區間。在這個區間以外,Trigger是不會被觸發的。

Trigger的實現類

SimpleTrigger

指定從某一個時間開始,以必定的時間間隔(單位是毫秒)執行的任務。

它適合的任務相似於:9:00 開始,每隔1小時,執行一次。

它的屬性有:

repeatInterval 重複間隔

repeatCount 重複次數。實際執行次數是 repeatCount+1。由於在startTime的時候必定會執行一次。

CronTrigger

適合於更復雜的任務,它支持類型於Linux Cron的語法(而且更強大)。基本上它覆蓋了以上三個Trigger的絕大部分能力(但不是所有)—— 固然,也更難理解。

它適合的任務相似於:天天0:00,9:00,18:00各執行一次。

它的屬性只有:

    • Cron表達式。雖然有在線生成器,可是仍是介紹一下

星號():可用在全部字段中,表示對應時間域的每個時刻,例如, 在分鐘字段時,表示「每分鐘」;

問號(?):該字符只在日期和星期字段中使用,它一般指定爲「無心義的值」,至關於點位符;

減號(-):表達一個範圍,如在小時字段中使用「10-12」,則表示從10到12點,即10,11,12;

逗號(,):表達一個列表值,如在星期字段中使用「MON,WED,FRI」,則表示星期一,星期三和星期五;

斜槓(/):x/y表達一個等步長序列,x爲起始值,y爲增量步長值。如在分鐘字段中使用0/15,則表示爲0,15,30和45秒,而5/15在分鐘字段中表示5,20,35,50,你也可使用*/y,它等同於0/y;

L:該字符只在日期和星期字段中使用,表明「Last」的意思,但它在兩個字段中意思不一樣。L在日期字段中,表示這個月份的最後一天,如一月的31號,非閏年二月的28號;若是L用在星期中,則表示星期六,等同於7。可是,若是L出如今星期字段裏,並且在前面有一個數值X,則表示「這個月的最後X天」,例如,6L表示該月的最後星期五;

W:該字符只能出如今日期字段裏,是對前導日期的修飾,表示離該日期最近的工做日。例如15W表示離該月15號最近的工做日,若是該月15號是星期六,則匹配14號星期五;若是15日是星期日,則匹配16號星期一;若是15號是星期二,那結果就是15號星期二。但必須注意關聯的匹配日期不可以跨月,如你指定1W,若是1號是星期六,結果匹配的是3號星期一,而非上個月最後的那天。W字符串只能指定單一日期,而不能指定日期範圍;

LW組合:在日期字段能夠組合使用LW,它的意思是當月的最後一個工做日;

井號(#):該字符只能在星期字段中使用,表示當月某個工做日。如6#3表示當月的第三個星期五(6表示星期五,#3表示當前的第三個),而4#5表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;

C:該字符只在日期和星期字段中使用,表明「Calendar」的意思。它的意思是計劃所關聯的日期,若是日期沒有被關聯,則至關於日曆中全部日期。例如5C在日期字段中就至關於日曆5日之後的第一天。1C在星期字段中至關於星期往後的第一天。

 

JobStore

 Quartz支持任務持久化,這可讓你在運行時增長任務或者對現存的任務進行修改,併爲後續任務的執行持久化這些變動和增長的部分。中心概念是JobStore接口。默認的是RAMJobStore。

 

ThreadTool

TriggerBuild

Scheduler

Calendar

Quartz體貼地爲咱們提供如下幾種Calendar,注意,全部的Calendar既能夠是排除,也能夠是包含,取決於:

HolidayCalendar。指定特定的日期,好比20140613。精度到天。

DailyCalendar。指定天天的時間段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度能夠到毫秒。

WeeklyCalendar。指定每星期的星期幾,可選值好比爲java.util.Calendar.SUNDAY。精度是天。

MonthlyCalendar。指定每個月的幾號。可選值爲1-31。精度是天

AnnualCalendar。 指定每一年的哪一天。使用方式如上例。精度是天。

CronCalendar。指定Cron表達式。精度取決於Cron表達式,也就是最大精度能夠到秒。

一個Trigger能夠和多個Calendar關聯,以排除或包含某些時間點

監聽器:

JobListener,TriggerListener,SchedulerListener

原生執行過程:

經過工廠建立一個Scheduler

建立一個實現Job接口的實現類(就是要具體作的事情,能夠具體調用本身寫的service)

定義一個Job,並綁定咱們本身實現Job接口的實現類(例如經過JobBuilder的方式)

建立Trigger,並設置相關參數,如啓動時間等。

將job和trigger綁定到scheduler對象上,並啓動

代碼略(網上一百度都有的HelloQuartz,我就懶得寫了)

四:深刻探究

原理:

quartz定時調度是經過Object.wait方式(native方法)實現的,其本質是經過操做系統的時鐘來實現的。

設計模式:

Bulild模式

組件模式

Factory模式

鏈式寫法

 線程視圖

在 Quartz 中,有兩類線程,Scheduler 調度線程和任務執行線程,其中任務執行線程一般使用一個線程池維護一組線程。

 

  Scheduler 調度線程主要有兩個: 執行常規調度的線程,和執行 misfired trigger 的線程。常規調度線程輪詢存儲的全部 trigger,若是有須要觸發的 trigger,即到達了下一次觸發的時間,則從任務執行線程池獲取一個空閒線程,執行與該 trigger 關聯的任務。Misfire 線程是掃描全部的 trigger,查看是否有 misfired trigger,若是有的話根據 misfire 的策略分別處理。下圖描述了這兩個線程的基本流程:

啓動流程

  若quartz是配置在spring中,當服務器啓動時,就會裝載相關的bean。SchedulerFactoryBean實現了InitializingBean接口,所以在初始化bean的時候,會執行afterPropertiesSet方法,該方法將會調用SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,一般用StdSchedulerFactory)建立Scheduler。SchedulerFactory在建立quartzScheduler的過程當中,將會讀取配置參數,初始化各個組件

集羣配置

  quartz集羣是經過數據庫表來感知其餘的應用的,各個節點之間並無直接的通訊。只有使用持久的JobStore才能完成Quartz集羣。
數據庫表:之前有12張表,如今只有11張表,如今沒有存儲listener相關的表,多了QRTZ_SIMPROP_TRIGGERS表:

QRTZ_LOCKS就是Quartz集羣實現同步機制的行鎖表,包括如下幾個鎖:CALENDAR_ACCESS 、JOB_ACCESS、MISFIRE_ACCESS 、STATE_ACCESS 、TRIGGER_ACCESS。

(篇幅有點長了,其餘的之後再出吧!)

相關文章
相關標籤/搜索