一、什么是quartz,有什么用。
Quartz是一個完全由java編寫的開源作業調度框架,由OpenSymphony組織開源出來。所謂作業調度其實就是按照程序的設定,某一時刻或者時間間隔去執行某個代碼。最常用的就是報表的制作了。
二、quartz的簡單事例
public class HelloJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { Object tv1 = context.getTrigger().getJobDataMap().get("t1"); Object tv2 = context.getTrigger().getJobDataMap().get("t2"); Object jv1 = context.getJobDetail().getJobDataMap().get("j1"); Object jv2 = context.getJobDetail().getJobDataMap().get("j2"); Object sv = null; try { sv = context.getScheduler().getContext().get("skey"); } catch (SchedulerException e) { e.printStackTrace(); } System.out.println(tv1+":"+tv2); System.out.println(jv1+":"+jv2); System.out.println(sv); System.out.println("hello:"+LocalDateTime.now()); } }
public class Test { public static void main(String[] args) throws SchedulerException { //創建一個scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.getContext().put("skey", "svalue"); //創建一個Trigger Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .usingJobData("t1", "tv1") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3) .repeatForever()).build(); trigger.getJobDataMap().put("t2", "tv2"); //創建一個job JobDetail job = JobBuilder.newJob(HelloJob.class) .usingJobData("j1", "jv1") .withIdentity("myjob", "mygroup").build(); job.getJobDataMap().put("j2", "jv2"); //注冊trigger並啟動scheduler scheduler.scheduleJob(job,trigger); scheduler.start(); } }
二、quartz的基本組成
1、調度器--------------Scheduler
Scheduler被用來對Trigger和Job進行管理。Trigger和JobDetail可以注冊到Scheduler中,兩者在Scheduler中都擁有自己的唯一的組和名稱用來進行彼此的區分,Scheduler可以通過組名或者名稱來對Trigger和JobDetail來進行管理。一個Trigger只能對應一個Job,但是一個Job可以對應多個Trigger。每個Scheduler都包含一個SchedulerContext,用來保存Scheduler的上下文。Job和Trigger都可以獲取SchedulerContext中的信息。
Scheduler包含兩個重要的組件,JobStore和ThreadPool。JobStore用來存儲運行時信息,包括Trigger,Schduler,JobDetail,業務鎖等。它有多種實現RAMJob(內存實現),JobStoreTX(JDBC,事務由Quartz管理)等。ThreadPool就是線程池,Quartz有自己的線程池實現。所有任務的都會由線程池執行。
Scheduler是由SchdulerFactory創建,它有兩個實現:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用來在代碼里定制你自己的Schduler參數。后者是直接讀取classpath下的quartz.properties(不存在就都使用默認值)配置來實例化Schduler。通常來講,我們使用StdSchdulerFactory也就足夠了
2、觸發器--------------Trigger
Trigger是用來定義Job的執行規則,主要有四種觸發器,其中SimpleTrigger和CronTrigger觸發器用的最多。
SimpleTrigger:從某一個時間開始,以一定的時間間隔來執行任務。它主要有兩個屬性,repeatInterval 重復的時間間隔;repeatCount 重復的次數,實際上執行的次數是n+1,因為在startTime的時候會執行一次。
CronTrigger:適合於復雜的任務,使用cron表達式來定義執行規則。
CalendarIntervalTrigger:類似於SimpleTrigger,指定從某一個時間開始,以一定的時間間隔執行的任務。 但是CalendarIntervalTrigger執行任務的時間間隔比SimpleTrigger要豐富,它支持的間隔單位有秒,分鍾,小時,天,月,年,星期。相較於SimpleTrigger有兩個優勢:1、更方便,比如每隔1小時執行,你不用自己去計算1小時等於多少毫秒。 2、支持不是固定長度的間隔,比如間隔為月和年。但劣勢是精度只能到秒。它的主要兩個屬性,interval 執行間隔;intervalUnit 執行間隔的單位(秒,分鍾,小時,天,月,年,星期)
DailyTimeIntervalTrigger:指定每天的某個時間段內,以一定的時間間隔執行任務。並且它可以支持指定星期。它適合的任務類似於:指定每天9:00 至 18:00 ,每隔70秒執行一次,並且只要周一至周五執行。它的屬性有startTimeOfDay 每天開始時間;endTimeOfDay 每天結束時間;daysOfWeek 需要執行的星期;interval 執行間隔;intervalUnit 執行間隔的單位(秒,分鍾,小時,天,月,年,星期);repeatCount 重復次數
所有的trigger都包含了StartTime和endTIme這兩個屬性,用來指定Trigger被觸發的時間區間。
所有的trigger都可以設置MisFire策略,該策略是對於由於系統奔潰或者任務時間過長等原因導致trigger在應該觸發的時間點沒有觸發,並且超過了misfireThreshold設置的時間(默認是一分鍾,沒有超過就立即執行)就算misfire了,這個時候就該設置如何應對這種變化了。激活失敗指令(Misfire Instructions)是觸發器的一個重要屬性,它指定了misfire發生時調度器應當如何處理。所有類型的觸發器都有一個默認的指令,叫做Trigger.MISFIRE_INSTRUCTION_SMART_POLICY,但是這個這個“聰明策略”對於不同類型的觸發器其具體行為是不同的。對於SimpleTrigger,這個“聰明策略”將根據觸發器實例的狀態和配置來決定其行 為。具體如下[]:
如果Repeat Count=0:只執行一次
instruction selected = MISFIRE_INSTRUCTION_FIRE_NOW;
如果Repeat Count=REPEAT_INDEFINITELY:無限次執行
instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT;
如果Repeat Count>0: 執行多次(有限)
instruction selected = MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT;
下面解釋SimpleTrigger常見策略:
MISFIRE_INSTRUCTION_FIRE_NOW 立刻執行。對於不會重復執行的任務,這是默認的處理策略。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 在下一個激活點執行,且超時期內錯過的執行機會作廢。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_COUNT 立即執行,且超時期內錯過的執行機會作廢。
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT 在下一個激活點執行,並重復到指定的次數。
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_COUNT 立即執行,並重復到指定的次數。
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY 忽略所有的超時狀態,按照觸發器的策略執行。
對於CronTrigger,該“聰明策略”默認選擇MISFIRE_INSTRUCTION_FIRE_ONCE_NOW以指導其行為。
下面解釋CronTrigger常見策略:
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 立刻執行一次,然后就按照正常的計划執行。
MISFIRE_INSTRUCTION_DO_NOTHING 目前不執行,然后就按照正常的計划執行。這意味着如果下次執行時間超過了end time,實際上就沒有執行機會了。
3、任務-----------------Job
Job是一個任務接口,開發者定義自己的任務須實現該接口實現void execute(JobExecutionContext context)方法,JobExecutionContext中提供了調度上下文的各種信息。Job中的任務有可能並發執行,例如任務的執行時間過長,而每次觸發的時間間隔太短,則會導致任務會被並發執行。如果是並發執行,就需要一個數據庫鎖去避免一個數據被多次處理。可以在execute()方法上添加注解@DisallowConcurrentExecution解決這個問題。
4、任務詳情-----------JobDetail
Quartz在每次執行Job時,都重新創建一個Job實例,所以它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時通過newInstance()的反射機制實例化Job。因此需要通過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。所以說JobDetail是任務的定義,而Job是任務的執行邏輯。
5、日歷------------------Calendar
Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日歷特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日歷時間點,無特殊說明后面的Calendar即指org.quartz.Calendar)。一個Trigger可以和多個Calendar關聯,以便排除或包含某些時間點。
主要有以下Calendar
HolidayCalendar。指定特定的日期,比如20140613。精度到天。
DailyCalendar。指定每天的時間段(rangeStartingTime, rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度可以到毫秒。
WeeklyCalendar。指定每星期的星期幾,可選值比如為java.util.Calendar.SUNDAY。精度是天。
MonthlyCalendar。指定每月的幾號。可選值為1-31。精度是天
AnnualCalendar。 指定每年的哪一天。使用方式如上例。精度是天。
CronCalendar。指定Cron表達式。精度取決於Cron表達式,也就是最大精度可以到秒。
6、JobDataMap
用來保存JobDetail運行時的信息,JobDataMap的使用:.usingJobData("name","kyle")或者.getJobDataMap("name","kyle")
7、cron表達式
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在星期字段中相當於星期日后的第一天。
8、quartz.properties文件編寫
####################RAMJob的配置################## #在集群中每個實例都必須有一個唯一的instanceId,但是應該有一個相同的instanceName【默認“QuartzScheduler”】【非必須】 org.quartz.scheduler.instanceName = MyScheduler #Scheduler實例ID,全局唯一,【默認值NON_CLUSTERED】,或者可以使用“SYS_PROP”通過系統屬性設置id。【非必須】 org.quartz.scheduler.instanceId=AUTO # 線程池的實現類(定長線程池,幾乎可滿足所有用戶的需求)【默認null】【必須】 org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool # 指定線程數,至少為1(無默認值)(一般設置為1-100直接的整數合適)【默認-1】【必須】 org.quartz.threadPool.threadCount = 25 # 設置線程的優先級(最大為java.lang.Thread.MAX_PRIORITY 10,最小為Thread.MIN_PRIORITY 1)【默認Thread.NORM_PRIORITY (5)】【非必須】 org.quartz.threadPool.threadPriority = 5 #misfire設置的時間默認為一分鍾 org.quartz.jobStore.misfireThreshold=60000 # 將schedule相關信息保存在RAM中,輕量級,速度快,遺憾的是應用重啟時相關信息都將丟失。 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore # 建議設置為“org.terracotta.quartz.skipUpdateCheck=true”不會在程序運行中還去檢查quartz是否有版本更新。【默認false】【非必須】 org.quartz.scheduler.skipUpdateCheck = true
###################jdbcJobStore############################ org.quartz.scheduler.instanceName=MyScheduler org.quartz.scheduler.instanceId=AUTO org.quartz.threadPool.threadCount=3 # 所有的quartz數據例如job和Trigger的細節信息被保存在內存或數據庫中,有兩種實現:JobStoreTX(自己管理事務) #和JobStoreCMT(application server管理事務,即全局事務JTA) org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX # 類似於Hibernate的dialect,用於處理DB之間的差異,StdJDBCDelegate能滿足大部分的DB org.quartz.jobStore.driverDelegateClass =org.quartz.impl.jdbcjobstore.StdJDBCDelegate #數據庫表的前綴 org.quartz.jobStore.tablePrefix=QRTZ_ #配置數據源的名稱 org.quartz.jobStore.dataSource=myDS #為了指示JDBCJobStore所有的JobDataMaps中的值都是字符串,並且能以“名字-值”對的方式存儲而不是以復雜對象的序列化形式存儲在BLOB字段中,應該設置為true(缺省方式) org.quartz.jobStore.useProperties = true # 檢入到數據庫中的頻率(毫秒)。檢查是否其他的實例到了應當檢入的時候未檢入這能指出一個失敗的實例, #且當前Scheduler會以此來接管執行失敗並可恢復的Job通過檢入操作,Scheduler也會更新自身的狀態記錄 org.quartz.jobStore.clusterCheckinInterval=20000 # 是否集群、負載均衡、容錯,如果應用在集群中設置為false會出錯 org.quartz.jobStore.isClustered=false #misfire時間設置 org.quartz.jobStore.misfireThreshold=60000 # 連接超時重試連接的間隔。使用 RamJobStore時,該參數並沒什么用【默認15000】【非必須】 #org.quartz.scheduler.dbFailureRetryInterval = 15000 #下面是數據庫鏈接相關的配置 org.quartz.dataSource.myDS.driver=com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL=jdbc:mysql://localhost:3306/zproject?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC org.quartz.dataSource.myDS.user=root org.quartz.dataSource.myDS.password=root org.quartz.dataSource.myDS.maxConnections=5
三、quartz的簡單使用
1、首先就是引入相關的jar包
2、編寫quartz.properies文件:參考上面的properties文件的編寫。
3、編寫代碼
@DisallowConcurrentExecution public class MyJob implements Job { @Override public void execute(JobExecutionContext arg0) throws JobExecutionException { String name = arg0.getJobDetail().getKey().getName(); System.out.println(name+"這是第一個定時任務"+LocalDateTime.now()); } }
public class Test { public static void main(String[] args) throws SchedulerException, InterruptedException { Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("MyJob","Group1").build(); JobDetail jobDetail2 = JobBuilder.newJob(MyJob.class).withIdentity("MyJob2","Group2").build(); JobDetail jobDetail3 = JobBuilder.newJob(MyJob.class).withIdentity("MyJob3","Group3").build(); JobDetail jobDetail4 = JobBuilder.newJob(MyJob.class).withIdentity("MyJob4","Group4").build(); Calendar cal1 = Calendar.getInstance(); cal1.set(Calendar.MINUTE, 28); cal1.set(Calendar.SECOND, 0); Calendar cal2 = Calendar.getInstance(); cal2.set(Calendar.MINUTE, 29); cal2.set(Calendar.SECOND, 0); DailyCalendar dailyCalendar = new DailyCalendar(cal1, cal2); scheduler.addCalendar("dailyCalendar", dailyCalendar, false, false); CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("tigger1","TGroup1") .withSchedule(CronScheduleBuilder.cronSchedule("/2 * * * * ?")).build(); SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger().withIdentity("trigger2","TGroup2") .withSchedule(SimpleScheduleBuilder.repeatSecondlyForTotalCount(5, 10)).build(); CalendarIntervalTrigger citrigger = TriggerBuilder.newTrigger().withIdentity("trigger3","TGroup3") .withSchedule(CalendarIntervalScheduleBuilder.calendarIntervalSchedule() .withIntervalInSeconds(10)).modifiedByCalendar("dailyCalendar").build(); DailyTimeIntervalTrigger dtiTrigger = TriggerBuilder.newTrigger().withIdentity("trigger4","TGroup4") .withSchedule(DailyTimeIntervalScheduleBuilder.dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 43)) .endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 44)) .withIntervalInSeconds(5) .withRepeatCount(20)) .build(); scheduler.scheduleJob(jobDetail, cronTrigger); scheduler.scheduleJob(jobDetail2, simpleTrigger); scheduler.scheduleJob(jobDetail3, citrigger); scheduler.scheduleJob(jobDetail4, dtiTrigger); scheduler.start(); } }
四、quartz的持久化中的表信息
quartz有兩種存儲方式,1.存儲到內存中,2.存儲到 數據庫中;下面是數據庫中各表的含義
qrtz_blob_triggers :這張表示存儲自己定義的trigger,不是quartz自己提供的trigger
qrtz_calendars :存儲Calendar
qrtz_cron_triggers :存儲cronTrigger
qrtz_fired_triggers :存儲觸發的Tirgger
qrtz_job_details :存儲JobDetail
qrtz_locks :存儲程序中非悲觀鎖的信息
qrtz_paused_trigger_grps :存儲已暫停的Trigger組信息
qrtz_scheduler_state :存儲集群中note實例信息,quartz會定時讀取該表的信息判斷集群中每個實例的當前狀態
qrtz_simple_triggers :存儲simpleTrigger的信息
qrtz_simprop_triggers :儲存CalendarIntervalTrigger和DailyTimeIntervalTrigger的信息
qrtz_triggers :存儲已配置的trigger信息