作業調度框架Quartz.net用法詳解
一、介紹
Quartz.NET是一個強大、開源、輕量的作業調度框架,你能夠用它來為執行一個作業而創建簡單的或復雜的作業調度。它有很多特征,如:數據庫支持,集群,插件,支持cron-like表達式等等。在2017年的最後一天Quartz.NET 3.0發佈,正式支持瞭.NET Core 和async/await。這是一個大版本,有眾多新特性和大的功能
官網:http://www.quartz-scheduler.net/
源碼:https://github.com/quartznet/quartznet
示例:https://www.quartz-scheduler.net/documentation/quartz-3.x/quick-start.html
新功能
- 支持 async/await 基於任務的作業,內部以async/await工作
- 支持.NET Core / netstandard 2.0和.NET Framework 4.5.2及更高版本
- 通過提供程序名稱SQLite-Microsoft支持Microsoft.Data.Sqlite,舊的提供程序SQLite也仍然有效,還可以用
- 增加瞭對SQL Server內存優化表的初步支持和Quartz.Impl.AdoJobStore.UpdateLockRowSemaphoreMOT
- 從依賴關系中刪除Common.Logging
- 刪除C5 Collections,使用.NET框架內置的Collections
- 在插件啟動時添加對作業調度XML文件的驗證
- 在TimeZoneUtil中添加對額外自定義時區解析器功能的支持
Quartz3.x和2.x最大的優勢在於:支持異步執行Job,所以建議將Job的Excute方法設計為異步方法,這樣做可以提高任務調度和執行效率。
quartz的構成和基本工作流程
在使用Quart.net前,我們先理解下quartz的構成和基本工作流程,
1、Quartz包含以下五個基本部分:
- Scheduler 調度器,quartz工作時的獨立容器
- Trigger 觸發器,定義瞭調度任務的時間規則
- Job 調度的任務
- ThreadPool 線程池(不是clr中的線程池),任務最終交給線程池中的線程執行
- JobStore RAWStore和DbStore兩種,job和trigger都存放在JobStore中
2、Quartz的基本工作流程:
scheduler是quartz的獨立運行容器,trigger和job都可以註冊在scheduler容器中。
其中trigger是觸發器,用於定義調度任務的時間規則,job是被調度的任務,一個job可以有多個觸發器,而一個觸發器隻能屬於一個job。
Quartz中一個調度線程QuartzSchedulerThread,調度線程可以找到將要被觸發的trigger,通過trigger找到要執行的job,然後在ThreadPool中獲取一個線程來執行這個job。
JobStore主要作用是存放job和trigger的信息。
二、基於文件配置
Quartz.NET 在3.x已經將插件分離瞭,所以如果要從xml直接加載文件,需要引入插件包
1、引入包
2、配置
app.config
<xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> <quartz> <add key="quartz.scheduler.instanceName" value="QuartzScheduler"/> <add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz"/> <add key="quartz.threadPool.threadCount" value="10"/> ******************************Plugin配置********************************************* <add key="quartz.plugin.jobInitializer.type" value="Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins"/> <add key="quartz.plugin.jobInitializer.fileNames" value="quartz_jobs.xml"/> </quartz> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/> </startup> </configuration>
quartz_jobs.xml
<xml version="1.0" encoding="UTF-8"?> This file contains job definitions in schema version 2.0 format <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> </processing-directives> <schedule> 開始執行一個調度 <job> <name>jldwjob</name> <group>kelun</group> <description>計量單位</description> 格式:實現瞭IJob接口的包含完整命名空間的類名,程序集名稱 <durable>true</durable> <recover>false</recover> </job> <trigger> <cron> <name>jlwd</name> <group>kelun</group> <job-name>jldwjob</job-name> <job-group>kelun</job-group> 2018-01-22T00:00:00+08:00 每3秒執行一次 </cron> </trigger> </schedule> </job-scheduling-data>
3、接口實現
using Kelun.Log4Net; using Quartz; using System.Reflection; using System.Threading.Tasks; namespace Kelun { class JldwJob : IJob { private static readonly IMyLog Logger = MyLogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public Task Execute(IJobExecutionContext context) { return Task.Run(() => { Logger.Info("定時任務執行"); }); } } }
4、啟動Quartz
public MainForm() { InitializeComponent(); RunProgramAsync().GetAwaiter().GetResult(); } private static async Task RunProgramAsync() { try { StdSchedulerFactory factory = new StdSchedulerFactory(); IScheduler scheduler = await factory.GetScheduler(); //開啟調度器 await scheduler.Start(); //創建一個作業 //IJobDetail job = JobBuilder.Create().WithIdentity("myJob", "group1").Build(); //ITrigger trigger = TriggerBuilder.Create() // .WithIdentity("myTrigger", "group1") // .StartNow() //現在開始 // .WithSimpleSchedule(x => x // .WithIntervalInSeconds(1) //觸發時間,1秒一次 // .RepeatForever()) // .Build(); //把作業,觸發器加入調度器。 //await scheduler.ScheduleJob(job, trigger); //await scheduler.Shutdown(); } catch (SchedulerException se) { Logger.Error("執行錯誤", se); } }
5、禁用Quartz.NET日志輸出
<logger name="Quartz"> <level value="OFF" /> </logger>
6、當應用或網站關閉時結束正在執行的工作
在Global.asax中的Application_End方法中添加如下代碼:
protected void Application_End(object sender, EventArgs e) { // 在應用程序關閉時運行的代碼 if (scheduler != null) { scheduler.Shutdown(true); } }
三、基於代碼的方式
用五分鐘看一個簡單的例子吧,這個例子中通過調度器工廠StdSchedulerFactory獲取一個調度器實例scheduler,然後定義Job和trigger,並註冊到調度器中,最後啟動scheduler就可以執行我們的任務瞭。代碼如下:
using Quartz; using Quartz.Impl; using System; using System.Threading.Tasks; namespace ConsoleApp3 { internal class Program { private static void Main(string[] args) { //調度器,生成實例的時候線程已經開啟瞭,不過是在等待狀態 StdSchedulerFactory factory = new StdSchedulerFactory(); IScheduler scheduler = factory.GetScheduler().Result; //創建一個Job,綁定MyJob IJobDetail job = JobBuilder.Create() //獲取JobBuilder .WithIdentity("jobname1", "group1") //添加Job的名字和分組 .WithDescription("一個簡單的任務") //添加描述 .Build(); //生成IJobDetail //創建一個觸發器 ITrigger trigger = TriggerBuilder.Create() //獲取TriggerBuilder .StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可使用StartNow() .ForJob(job) //將觸發器關聯給指定的job .WithPriority(10) //優先級,當觸發時間一樣時,優先級大的觸發器先執行 .WithIdentity("tname1", "group1") //添加觸發器的名字和分組 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //調度,一秒執行一次,執行三次 .WithRepeatCount(3) .Build()) .Build(); //start讓調度線程啟動【調度線程可以從jobstore中獲取快要執行的trigger,然後獲取trigger關聯的job,執行job】 scheduler.Start(); //將job和trigger註冊到scheduler中 scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob /// /// 一個簡單的Job,所有的Job都要實現IJob接口 /// public class MyJob : IJob { public async Task Execute(IJobExecutionContext context) { await Task.Run(() => { Console.WriteLine("hello quartz!"); //JobDetail的key就是job的分組和job的名字 Console.WriteLine($"JobDetail的組和名字:{context.JobDetail.Key}"); Console.WriteLine(); }); } } #endregion MyJob }
通過Quartz來調度一個任務十分簡單,執行結果如下:
四、Quartz.net的概念和基本用法
1.TriggerBuilder介紹
在Quartz中Trigger的作用是定義Job何時執行。
Quartz.net提供瞭四種觸發策略:SimpleSchedule,CalendarIntervalSchedule,DailyTimeIntervalSchedule和CronSchedule。
TriggerBuilder顧名思義就是用來創建Trigger的。
1、SimpleSchedule
Simpleschedule 是最簡單的一種觸發策略,它的作用類似於timer,可以設置間隔幾秒/幾分鐘/幾小時執行一次,如創建一秒執行一次的觸發器如下
ITrigger trigger = TriggerBuilder.Create() .StartNow() .WithIdentity("tname1", "group1") .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) //設置時間間隔,時分秒 .WithRepeatCount(3)) //設置執行次數,總共執行3+1次, .Build();
2、CalendarIntervalSchedule
CalendarIntervalSchedule擴展瞭Simplescheduler的功能,Simplescheduler隻能在時分秒的維度上指定時間間隔,那麼就有一個問題,如果我們想一個月執行一次怎麼辦呢?要知道每個月的天數是不一樣的,用SimpleSchedule實現起來就十分麻煩瞭。
CalendarIntervalSchedule可以實現時分秒天周月年的維度上執行輪詢。如創建一個月執行一次的觸發器如下:這樣
ITrigger trigger = TriggerBuilder.Create() .StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00")) .WithIdentity("tname1", "group1") .WithCalendarIntervalSchedule(x => x.WithIntervalInMonths(1)) //一月執行一次 .Build();
3、DailyTimeIntervalSchedule
DailyTimeIntervalSchedule主要用於指定每周的某幾天執行,如我們想讓每周的周六周日的8:00-20:00,每兩秒執行一次,創建觸發器如下:
ITrigger trigger =TriggerBuilder.Create() .StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00")) .WithIdentity("tname1", "group1") .WithDailyTimeIntervalSchedule(x => x.OnDaysOfTheWeek(new DayOfWeek[] { DayOfWeek.Saturday, DayOfWeek.Sunday }) //周六和周日 .StartingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(8, 00, 00)) //8點開始 .EndingDailyAt(TimeOfDay.HourMinuteAndSecondOfDay(20, 00, 00)) //20點結束 .WithIntervalInSeconds(2) //兩秒執行一次,可設置時分秒維度 .WithRepeatCount(3)) //一共執行3+1次 .Build();
4、CronSchedule
CronSchedule是應用最多的觸發策略,通過Cron表達是我們可以輕松地表示任意的時間節點,下邊的代碼創建瞭一個每隔5秒執行一次的觸發器
ITrigger trigger = TriggerBuilder.Create() .StartAt(DateTimeOffset.Parse("2018/1/6 13:17:00")) .WithIdentity("tname1", "group1") .WithCronSchedule("3/5 * * * * ?") //五秒執行一次 .Build();
5、Cron表達式
cron表達式有七個部分組成,以此是秒、分、時、天、月、周、年,其中年是可選的。Cron表達式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。
- 星號(*):可用在所有字段中,表示對應時間域的每一個時刻。例如, 在分鐘字段時,表示“每分鐘”;
- 問號(?):該字符隻在日期和星期字段中使用,它通常指定為“無意義的值”,相當於點位符;
- 減號(-):表達一個范圍,如在小時字段中使用“10-12”,則表示從10到12點,即10,11,12;
- 逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;
- 斜杠(/):x/y表達一個等步長序列,x為起始值,y為增量步長值。如5/15在分鐘字段中表示5,20,35,50;
- 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表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;
一些例子:
- 0 0 12 * * ?:每天12點運行
- 0 15 10 ? * *:每天10:15運行
- 0 15 10 * * ?:每天10:15運行
- 0 15 10 * * ? *:每天10:15運行
- 0 15 10 * * ? 2008:在2008年的每天10:15運行
- 0 * 14 * * ?:每天14點到15點之間每分鐘運行一次,開始於14:00,結束於14:59。
- 0 0/5 14 * * ?:每天14點到15點每5分鐘運行一次,開始於14:00,結束於14:55。
- 0 0/5 14,18 * * ?:每天14點到15點每5分鐘運行一次,此外每天18點到19點每5鐘也運行一次。
- 0 0-5 14 * * ?:每天14:00點到14:05,每分鐘運行一次。
- 0 10,44 14 ? 3 WED:3月每周三的14:10分和14: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 2007-2009:在2007,2008,2009年每個月的最後一個星期五的10:15分運行。
- 0 15 10 ? * 6#3:每月第三個星期五的10:15分運行。
2.Scheduler介紹
調度器scheduler是Quartz中的獨立工作容器,所有的Trigger和Job都需要註冊到scheduler中才能工作。我們可以通過SchedulerFactory來獲取scheduler實例。如下:
//1.獲取默認的標準Scheduler引用 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; //2.通過代碼配置scheduler NameValueCollection properties = new NameValueCollection { //scheduler的名字 ["quartz.scheduler.instanceName"] = "MyScheduler", // 設置線程池中線程個數為20個 ["quartz.threadPool.threadCount"] = "20", ["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz", //JobStore類型為內存存儲 ["quartz.jobStore.type"] = "Quartz.Simpl.RAMJobStore, Quartz" }; ISchedulerFactory factroy = new StdSchedulerFactory(properties); IScheduler scheduler= await factroy .GetScheduler();
這裡列一些會經常用的到方法,方法比較簡單直觀,就不一一展示瞭。
scheduler.PauseJob(JobKey.Create("jobname", "groupname"));//暫停job scheduler.ResumeJob(JobKey.Create("jobname", "groupname"));//重新啟動job scheduler.DeleteJob(JobKey.Create("jobname", "groupname"));//刪除job scheduler.PauseTrigger(new TriggerKey("triggername", "groupname"));//暫停trigger scheduler.ResumeTrigger(new TriggerKey("triggername", "groupname"));//重新啟動trigger scheduler.UnscheduleJob(new TriggerKey("triggername", "groupname"));//刪除trigger scheduler.GetTriggersOfJob(JobKey.Create("jobname", "groupname"));//獲取一個job的所有key scheduler.Standby(); //暫停所有的觸發器,可通過shceduler.Start()重啟 scheduler.Shutdown(); //關閉scheduler,釋放資源。通過Shutdown()關閉後,不能通過Start()重啟 scheduler.GetMetaData();//獲取scheduler的元數據 scheduler.Clear();//清空容器中所有的IJob,ITrigger //調度多個任務 Dictionary> triggersAndJobs = new Dictionary>(); triggersAndJobs.Add(job1, new List() { trigger1,trigger2}); triggersAndJobs.Add(job2, new List() { trigger3}); await scheduler.ScheduleJobs(triggersAndJobs, true);
3.Calendar介紹
通過上邊的介紹,我們知道通過觸發器Trigger可以設置Job的執行時間,但是有時候隻使用Trigger來調度比較麻煩,如一年中每天都執行,但是元旦、聖誕節和情人節這三天不執行。使用trigger也可以實現,但是比較麻煩,如果我們有一種方法可以方便地排除這三天就好辦瞭,Calendar主要作用就是為瞭排除Trigger中一些特定的時間節點。看一個簡單的栗子:
class Program { static void Main(string[] args) { //調度器 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result; //JobDetail IJobDetail job = JobBuilder .Create() .Build(); //獲取一個Calendar實例 DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執行 //將Calendar註冊到Scheduler中 scheduler.AddCalendar("myCalendar", calendar, true, true);//參數依次是:calendarname,calendar,是否替換同名clendar,是否更新trigger //獲取一個觸發器,並將calendar綁定到觸發器上 ITrigger trigger = TriggerBuilder.Create() .StartNow() .WithIdentity("tname1", "group1") .WithCronSchedule("0/2 * * * * ?") //2秒執行一次 .ModifiedByCalendar("myCalendar") //把Calendar綁定到trigger .Build(); //start讓調度線程啟動 scheduler.Start(); //將job添加到調度器中,同時將trigger綁定到job scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob,繼承IJob接口 /// /// 一個簡單的Job /// public class MyJob : IJob {public Task Execute(IJobExecutionContext context) { return Task.Run(() => { Console.WriteLine($"觸發時間:{context.ScheduledFireTimeUtc?.LocalDateTime},下次觸發時間:{context.NextFireTimeUtc?.LocalDateTime}"); }); } } #endregion
執行結果:
使用Calendar的流程是:首先獲取一個Calendar實例,然後將Calendar註冊到scheduler容器中,在將Calendar綁定到觸發器上即可。
Quartz.net中一共提供瞭六種Calendar,六種Calendar的用法大同小異,列舉如下:
【1】DailyCalendar 用於排除一天中的某一段時間
DailyCalendar calendar = new DailyCalendar(DateBuilder.DateOf(19, 0, 0).DateTime, DateBuilder.DateOf(23, 0, 0).DateTime);//21~23點不執行
【2】WeeklyCalendar 用於排除一周中的某幾天
WeeklyCalendar calendar = new WeeklyCalendar(); calendar.SetDayExcluded(DayOfWeek.Sunday, true);//周日不執行 //註:如果想讓周日恢復執行,執行代碼: calendar.SetDayExcluded(DayOfWeek.Sunday, false);
【3】HolidayCalendar 用於排除某些日期
HolidayCalendar calendar = new HolidayCalendar(); calendar.AddExcludedDate(DateTime.Parse("2018/1/2")); //2018年1月2號不執行 //註:如果想讓2019/1/9恢復執行,執行代碼: calendar.RemoveExcludedDate(DateTime.Parse("2018/1/2"));
【4】MonthlyCalendar 用於排除每個月的某天*************************************
MonthlyCalendar calendar = new MonthlyCalendar(); calendar.SetDayExcluded(8, true); //每個月的8號不執行 //註:如果想讓8號恢復執行,執行代碼: calendar.SetDayExcluded(8, false);
【5】AnnualCalendar 用於排除一年中的某些天*************************************
AnnualCalendar calendar = new AnnualCalendar(); calendar.SetDayExcluded(DateTime.Parse("2018/1/2"), true);//每年1月2號不執行 //註:如果想讓1月8號恢復執行,執行代碼: calendar.SetDayExcluded(DateTime.Parse("2018/1/2"),true);
【6】CronCalendar 用於排除cron表達式表示的時間***************************
CronCalendar calendar = new CronCalendar("* * * 2 1 ?"); //每年的1月2號不執行
4.Listener介紹
TriggerListener和JobListener用法類似,這裡以JobListener為例來介紹Quartz中的Listener。JobListener用於在Job執行前、後和被拒絕時執行一些動作,和Asp.net中的filter很相似,用法並不復雜,看一個簡單的栗子:
using Quartz; using Quartz.Impl; using Quartz.Impl.Matchers; using System; using System.Collections.Specialized; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp3 { internal class Program { private static void Main(string[] args) { //獲取調度器 NameValueCollection pairs = new NameValueCollection() { { "quartz.threadPool.ThreadCount", "30" } }; StdSchedulerFactory factory = new StdSchedulerFactory(pairs); IScheduler scheduler = factory.GetScheduler().Result; //創建Job IJobDetail job = JobBuilder .Create() //獲取JobBuilder .WithIdentity("jobname1", "group1") //添加Job的名字和分組 .Build(); //生成IJobDetail //創建rigger ITrigger trigger = TriggerBuilder.Create() //獲取JobBuilder .StartAt(DateBuilder.TodayAt(01, 00, 00)) //開始時間,今天的1點(hh,mm,ss),可使用StartNow() .WithPriority(10) //優先級,當觸發時間一樣時,優先級大的觸發器先執行 .WithIdentity("tname1", "group1") //添加名字和分組 .WithSimpleSchedule(x => x.WithIntervalInSeconds(1) .RepeatForever() .Build()) .Build(); //啟動調度器 scheduler.Start(); //myJobListener監控所有的job scheduler.ListenerManager.AddJobListener(new MyJobListener(), GroupMatcher.AnyGroup()); //將job添加到調度器中,同時將trigger綁定到job scheduler.ScheduleJob(job, trigger).Wait(); Console.ReadKey(); } } #region MyJob,繼承IJob接口 /// /// 一個簡單的Job /// public class MyJob : IJob { public async Task Execute(IJobExecutionContext context) { await Task.Run(() => { Console.WriteLine("hello quartz!"); Console.WriteLine($"ThreadPool中的線程個數:{context.Scheduler.GetMetaData().Result.ThreadPoolSize}"); }); } } #endregion MyJob,繼承IJob接口 #region myJobListener,繼承IJobListener接口 /// /// 一個簡單的JobListener,triggerListener類似 /// public class MyJobListener : IJobListener { public string Name => "hello joblisener"; //job被拒絕時執行 public async Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { }); } //job開始前執行 public async Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { Console.WriteLine("myjob-------------begin"); }); } //job完成後執行 public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default(CancellationToken)) { await Task.Run(() => { Console.WriteLine("myjob---------------end"); Console.WriteLine(); }); } #endregion myJobListener,繼承IJobListener接口 } }
上邊例子中,Listener執行的動作很簡單,在Job執行前打印begin,執行後打印end,在實際開發中,我們可以在通過Listenter來記錄job的執行日志,執行結果如下:
五、Quartz.net通過數據庫持久化與配置集群。
1.JobStore介紹
學習持久化和集群前我們先瞭解一下Quartz.net中的JobStore,JobStore用於追蹤任務調度相關的所有數據,如Job,Trigger,Calendar等。
Quartz.net 提供瞭兩種JobStore:RAMJobStore,AdoJobStore。
1、RAMJobStore:RAMJobStore是最簡單的JobStore,顧名思義這種JobStore將所有的數據都存放在內存中,這也是它運行速度快的原因,但是弊端也很明顯:一旦應用結束或者遇到斷電所有的數據都會丟失。
RAMJobStore是默認的JobStore,我們也已通過下邊的代碼來顯式設置使用的JobStore為RAMJobStore。
quartz.jobStore.type = Quartz.Simpl.RAMJobStore, Quartz
2、AdoJobStore:AdoJobStore通過Ado.net將數據存儲在數據庫中,因此可以解決斷電數據丟失的問題,但是因為要讀寫數據庫所以效率相對較低。
AdoJobStore官方支持的數據庫有:MySql,SqlServer,Sqllite,Oracle等,當前AdoJobStore隻有一種類型JobStoreTX,這一點不同於Jave版本,java版本還有JobStoreCMT類型。
2.Db持久化和集群配置
Quartz.net配置數據庫持久化和集群比較容易,可以分為簡單的兩步:
第一步:添加數據庫表
我們首先要在數據庫中添加一系列的表(在Quartz項目的database/tables文件夾下可以找到各種數據庫表的生成腳本,Git地址https://github.com/quartznet/quartznet/tree/master/database/tables)。以Sqlserver為例,Sqlserver的數據表生成腳本如下:
(略)
在Sqlserver管理器中新建查詢,粘貼上邊的腳本,運行即可生成Quartz的數據庫表,數據庫的結構如下:
第二步:配置QuartzFactory屬性,直接看代碼:
class Program { public static void Main(string[] args) { NameValueCollection pars = new NameValueCollection { //scheduler名字 ["quartz.scheduler.instanceName"] = "MyScheduler", //線程池個數 ["quartz.threadPool.threadCount"] = "20", //類型為JobStoreXT,事務 ["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz", //JobDataMap中的數據都是字符串 //["quartz.jobStore.useProperties"] = "true", //數據源名稱 ["quartz.jobStore.dataSource"] = "myDS", //數據表名前綴 ["quartz.jobStore.tablePrefix"] = "QRTZ_", //使用Sqlserver的Ado操作代理類 ["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz", //數據源連接字符串 ["quartz.dataSource.myDS.connectionString"] = "Server=[yourserver];Database=quartzDb;Uid=sa;Pwd=[yourpass]", //數據源的數據庫 ["quartz.dataSource.myDS.provider"] = "SqlServer", //序列化類型 ["quartz.serializer.type"] = "json",//binary //自動生成scheduler實例ID,主要為瞭保證集群中的實例具有唯一標識 ["quartz.scheduler.instanceId"] = "AUTO", //是否配置集群 ["quartz.jobStore.clustered"] = "true", }; StdSchedulerFactory factory = new StdSchedulerFactory(pars); IScheduler scheduler = factory.GetScheduler().Result; scheduler.Start(); IJobDetail job = JobBuilder.Create() .WithIdentity("job1", "g1") .Build(); ITrigger trigger = TriggerBuilder.Create().WithIdentity("trigger1", "g1").WithCronSchedule("0/1 * * * * ?").Build(); if (scheduler.CheckExists(job.Key).Result) { scheduler.DeleteJob(job.Key).Wait(); } scheduler.ScheduleJob(job, trigger).Wait(); } } public class MyJob : IJob { public async Task Execute(IJobExecutionContext context) { await Task.Run(() => { Console.WriteLine($"hello! 當前時間:{DateTime.Now}"); Console.WriteLine($"觸發時間:{context.ScheduledFireTimeUtc?.LocalDateTime},下次觸發時間:{context.NextFireTimeUtc?.LocalDateTime}"); Console.WriteLine(); }); } }
上邊的代碼配置信息在代碼中都有註釋,這個例子實現瞭一個簡單的任務調度:每秒打印一次任務的觸發時間和下次觸發時間。然後運行項目即可,到這裡Db持久化和集群都配置完成瞭。運行程序Quartz會自動在數據庫中記錄調度任務相關的數據。
Quartz自動向數據庫寫入的trigger信息:
Quartz自動向數據庫寫入的Job信息:
程序執行結果:
到這裡我們看到瞭Db持久化已經實現瞭,但是上邊的例子,我們在代碼中通過 ["quartz.jobStore.clustered"] = "true" 配置瞭集群,這個有什麼用呢?
首先添加一個debug文件夾的副本
然後運行這兩個文件夾下的xxx.exe文件(如果使用的是.net core,生成的是xxx.dll文件,進入dll文件所在目錄,命令行運行 dotnet xxx.dll 即可啟動),運行結果如下:
如上所示,運行兩個xxx.exe(core中是dll)後,原文件和副本在同一時間隻有一個在運行,所以我們調度的任務沒有重復執行。如果我們關掉正在執行的那個程序,那麼另一個程序會開始執行。我們可以得出結論:Quartz的集群並不會造成任務重復執行,而且當一個服務器掛瞭後,另一個服務器會自動開始執行,這種機制大大增加瞭任務調度的容災性能。
3.一些需要註意的地方
1.Quartz3.x支持async和await,為提高性能,我們最好將Job中的Execute方法都寫成異步方法;
2.不管使用的是RAMJobStore還是AdoJobStore,千萬不要通過代碼來直接操作JobStore(比如我們直接通過代碼修改數據庫中的數據),JobStore讓Quartz自動操作即可。
無論使用場景是web應用還是桌面程序,我們隻使用Scheduler提供的接口方法來實現Job和Trigger等的增/刪/改/查/暫停/恢復即可。
到此這篇關於作業調度框架Quartz.net用法的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- Quartz在.NET中的使用教程
- Quartz.Net使用方法詳解
- springboot整合quartz項目使用案例
- Spring Boot 配置 Quartz 定時任務的方法
- Java 任務調度框架 Quartz實操