Quartz.Net使用教程


Quartz.Net使用教程

在項目的開發過程當中,不免會碰見後須要後臺處理的任務,例如定時發送郵件通知、後臺處理耗時的數據處理等,這個時候你就須要Quartz.Net了。javascript

Quartz.Net是純淨的,它是一個.Net程序集,是很是流行的Java做業調度系統Quartz的C#實現。css

Quartz.Net一款功能齊全的任務調度系統,從小型應用到大型企業級系統都能適用。功能齊全體如今觸發器的多樣性上面,即支持簡單的定時器,也支持Cron表達式;即能執行重複的做業任務,也支持指定例外的日曆;任務也能夠是多樣性的,只要繼承IJob接口便可。java

對於小型應用,Quartz.Net能夠集成到你的系統中,對於企業級系統,它提供了Routing支持,提供了Group來組織和管理任務,此外還有持久化、插件功能、負載均衡和故障遷移等知足不一樣應用場景的須要。git

Hello Quartz.Net

開始使用一個框架,和學習一門開發語言同樣,最好是從Hello World程序開始。github

首先建立一個示例程序,而後添加Quartz.Net的引用。sql

Install-Package Quartz -Version 3.0.7

咱們使用的是當前最新版本3.0.7進行演示。添加引用之後,來建立一個Job類HelloQuartzJobshell

public class HelloQuartzJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Hello Quartz.Net");
});
}
}

這是個很是簡單的Job類,它在執行時輸出文本Hello Quartz.Net數據庫

接下來,咱們在程序啓動時建立調度器(Scheduler),並添加HelloQuartzJob的調度:windows

static async Task MainAsync()
{
var schedulerFactory = new StdSchedulerFactory();
var scheduler = await schedulerFactory.GetScheduler();
await scheduler.Start();
Console.WriteLine($"任務調度器已啓動");

//建立做業和觸發器
var jobDetail = JobBuilder.Create<HelloQuartzJob>().Build();
var trigger = TriggerBuilder.Create()
.WithSimpleSchedule(m => {
m.WithRepeatCount(3).WithIntervalInSeconds(1);
})
.Build();

//添加調度
await scheduler.ScheduleJob(jobDetail, trigger);
}

而後運行程序,你會看到以下圖:
bash

經過演示能夠看出,要執行一個定時任務,通常須要四步:

  1. 建立任務調度器。調度器一般在應用程序啓動時建立,一個應用程序實例一般只須要一個調度器便可。

  2. 建立Job和JobDetail。Job是做業的類型,描述了做業是如何執行的,這個類是由咱們定義的;JobDetail是Quartz對做業的封裝,它包含Job類型,以及Job在執行時用到的數據,還包括是否要持久化、是否覆蓋已存在的做業等選項。

  3. 建立觸發器。觸發器描述了在什麼時候執行做業。

  4. 添加調度。當完成以上三步之後,就能夠對做業進行調度了。

做業:Job和JobDetail

Job是做業的類型,描述了做業是如何執行的,這個類型是由咱們定義的,例如上文的HelloQuartzJob。Job實現IJob接口,而IJob接口只有一個Execute方法,參數context中包含了與當前上下文中關聯的Scheduler、JobDetail、Trigger等。

一個典型的Job定義以下:

public class HelloQuartzJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine("Hello Quartz.Net");
});
}
}

JobData

Job不是孤立存在的,它須要執行的參數,這些參數如何傳遞進來呢?咱們來定義一個Job類進行演示。

public class SayHelloJob : IJob
{
public string UserName { get; set; }

public Task Execute(IJobExecutionContext context)
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine($"Hello {UserName}!");
});
}
}

SayHelloJob在執行時須要參數UserName,這個參數被稱爲JobData,Quartz.Net經過JobDataMap的方式傳遞參數。代碼以下:

//建立做業
var jobDetail = JobBuilder.Create<SayHelloJob>()
.SetJobData(new JobDataMap() {
new KeyValuePair<string, object>("UserName", "Tom")
})
.Build();

經過JobBuilder的SetJobData方法,傳入JobDataMap對象,JobDataMap對象中能夠包含多個參數,這些參數能夠映射到Job類的屬性上。咱們完善代碼運行示例,能夠看到以下圖:

JobDetail

JobDetail是Quartz對做業的封裝,它包含Job類型,以及Job在執行時用到的數據,還包括是否孤立存儲、請求恢復做業等選項。

JobDetail是經過JobBuilder進行建立的。例如:

var jobDetail = JobBuilder.Create<SayHelloJob>()
.SetJobData(new JobDataMap() {
new KeyValuePair<string, object>("UserName", "Tom")
})
.StoreDurably(true)
.RequestRecovery(true)
.WithIdentity("SayHelloJob-Tom", "DemoGroup")
.WithDescription("Say hello to Tom job")
.Build();

參數說明:

  • SetJobData:設置JobData

  • StoreDurably:孤立存儲,指即便該JobDetail沒有關聯的Trigger,也會進行存儲

  • RequestRecovery:請求恢復,指應用崩潰後再次啓動,會從新執行該做業

  • WithIdentity:做業的惟一標識

  • WithDescription:做業的描述信息

除此以外,Quartz.Net還支持兩個很是有用的特性:

  • DisallowConcurrentExecution:禁止並行執行,該特性是針對JobDetail生效的

  • PersistJobDataAfterExecution:在執行完成後持久化JobData,該特性是針對Job類型生效的,意味着全部使用該Job的JobDetail都會在執行完成後持久化JobData。

持久化JobData

咱們來演示一下該PersistJobDataAfterExecution特性,在SayHelloJob中,咱們新加一個字段RunSuccess,記錄任務是否執行成功。

首先在SayHelloJob添加特性:

[PersistJobDataAfterExecution]
public class SayHelloJob : IJob { }

而後在建立JobDetail時添加JobData:

var jobDetail = JobBuilder.Create<SayHelloJob>()
.SetJobData(new JobDataMap() {
new KeyValuePair<string, object>("UserName", "Tom"),
new KeyValuePair<string, object>("RunSuccess", false)
})

在執行時Job時,更新RunSuccess的值:

public Task Execute(IJobExecutionContext context)
{
return Task.Factory.StartNew(() =>
{
Console.WriteLine($"Prev Run Success:{RunSuccess}");
Console.WriteLine($"Hello {UserName}!");

context.JobDetail.JobDataMap.Put("RunSuccess", true);
});
}

接下來看一下執行效果:

觸發器:Trigger

Trigger是觸發器,用來定製執行做業。Trigger有兩種類型:SampleTrigger和CronTrigger,咱們分別進行說明。

SampleTrigger

顧名思義,這是個簡單的觸發器,有如下特性:

  • 重複執行:WithRepeatCount()/RepeatForever()

  • 設置間隔時間:WithInterval()

  • 定時執行:StartAt()/StartNow()

  • 設定優先級:WithPriority(),默認爲5

須要注意:當Trigger到達StartAt指定的時間時會執行一次,這一次執行是不包含在WithRepeatCount中的。在咱們上面的例子中能夠看出,添加調度後會當即執行一次,而後重複三次,最終執行了四次。

CronTrigger

CronTrigger是經過Cron表達式來完成調度的。Cron表達式很是靈活,能夠實現幾乎各類定時場景的須要。

關於Cron表達式,你們能夠移步 Quartz Cron表達式

使用CronTrigger的示例以下:

var trigger = TriggerBuilder.Create()
.WithCronSchedule("*/1 * * * * ?")
.Build();

日曆:Calendar

Calendar能夠與Trigger進行關聯,從Trigger中排出執行計劃。例如你只但願在工做日執行做業,那麼咱們能夠定義一個休息日的日曆,將它與Trigger關聯,從而排出休息日的執行計劃。

Calendar示例代碼以下:

var calandar = new HolidayCalendar();
calandar.AddExcludedDate(DateTime.Today);

await scheduler.AddCalendar("holidayCalendar", calandar, false, false);

var trigger = TriggerBuilder.Create()
.WithCronSchedule("*/1 * * * * ?")
.ModifiedByCalendar("holidayCalendar")
.Build();

在這個示例中,咱們建立了HolidayCalendar日曆,而後添加排除執行的日期。咱們把今天添加到排除日期後,該Trigger今天將不會觸發。

監聽器:JobListeners/TriggerListeners/SchedulerListeners

監聽器是Quartz.Net的另一個出色的功能,它容許咱們編寫監聽器達到在運行時獲取做業狀態、處理做業數據等功能。

JobListener

JobListener能夠監聽Job執行前、執行後、否決執行的事件。咱們經過代碼進行演示:

public class MyJobListener : IJobListener
{
public string Name { get; } = nameof(MyJobListener);

public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default)
{
//Job即將執行
return Task.Factory.StartNew(() =>
{
Console.WriteLine($"Job: {context.JobDetail.Key} 即將執行");
});
}

public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)
{
return Task.Factory.StartNew(()=> {
Console.WriteLine($"Job: {context.JobDetail.Key} 被否決執行");
});
}

public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException, CancellationToken cancellationToken = default)
{
//Job執行完成
return Task.Factory.StartNew(() =>
{
Console.WriteLine($"Job: {context.JobDetail.Key} 執行完成");
});
}
}

定義完成後,將MyJobListener添加到Scheduler中:

scheduler.ListenerManager.AddJobListener(new MyJobListener(), GroupMatcher<JobKey>.AnyGroup());

而後咱們再運行程序,就能夠看到Listener被調用了:

經過圖片能夠看到,JobToBeExecutedJobWasExecuted都被執行了,JobExecutionVetoed沒有執行,那麼如何觸發JobExecutionVetoed呢?請繼續閱讀TriggerListener的演示。

TriggerListener

TriggerListener能夠監聽Trigger的執行狀況,咱們經過代碼進行演示:

public class MyTriggerListener : ITriggerListener
{
public string Name { get; } = nameof(MyTriggerListener);

public Task TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}

public Task TriggerFired(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}

public Task TriggerMisfired(ITrigger trigger, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}

public Task<bool> VetoJobExecution(ITrigger trigger, IJobExecutionContext context, CancellationToken cancellationToken = default)
{
return Task.FromResult(true); //返回true表示否決Job繼續執行
}
}

MyTriggerListener添加到Scheduler中:

scheduler.ListenerManager.AddTriggerListener(new MyTriggerListener(), GroupMatcher<TriggerKey>.AnyGroup());

運行代碼能夠看到以下效果:

從圖片中能夠看到,JobListener中的JobExecutionVetoed被執行了。

SchedulerListener

ISchedulerListener提供了Job、Trigger管理的監聽,與調度程序相關的事件包括:添加做業/觸發器,刪除做業/觸發器,調度程序中的嚴重錯誤,調度程序關閉的通知等。完整的接口定義以下:

public interface ISchedulerListener
{
Task JobAdded(IJobDetail jobDetail, CancellationToken cancellationToken = default);
Task JobDeleted(JobKey jobKey, CancellationToken cancellationToken = default);
Task JobInterrupted(JobKey jobKey, CancellationToken cancellationToken = default);
Task JobPaused(JobKey jobKey, CancellationToken cancellationToken = default);
Task JobResumed(JobKey jobKey, CancellationToken cancellationToken = default);
Task JobScheduled(ITrigger trigger, CancellationToken cancellationToken = default);
Task JobsPaused(string jobGroup, CancellationToken cancellationToken = default);
Task JobsResumed(string jobGroup, CancellationToken cancellationToken = default);
Task JobUnscheduled(TriggerKey triggerKey, CancellationToken cancellationToken = default);
Task SchedulerError(string msg, SchedulerException cause, CancellationToken cancellationToken = default);
Task SchedulerInStandbyMode(CancellationToken cancellationToken = default);
Task SchedulerShutdown(CancellationToken cancellationToken = default);
Task SchedulerShuttingdown(CancellationToken cancellationToken = default);
Task SchedulerStarted(CancellationToken cancellationToken = default);
Task SchedulerStarting(CancellationToken cancellationToken = default);
Task SchedulingDataCleared(CancellationToken cancellationToken = default);
Task TriggerFinalized(ITrigger trigger, CancellationToken cancellationToken = default);
Task TriggerPaused(TriggerKey triggerKey, CancellationToken cancellationToken = default);
Task TriggerResumed(TriggerKey triggerKey, CancellationToken cancellationToken = default);
Task TriggersPaused(string triggerGroup, CancellationToken cancellationToken = default);
Task TriggersResumed(string triggerGroup, CancellationToken cancellationToken = default);
}

添加SchedulerListener的代碼以下:

scheduler.ListenerManager.AddSchedulerListener(mySchedListener);

持久化:JobStore

Quartz.Net支持Job的持久化操做,被稱爲JobStore。默認狀況下,Quartz將數據持久化到內存中,好處是內存的速度很快,壞處是沒法提供負載均衡的支持,而且在程序崩潰後,咱們將丟失全部Job數據,對於企業級系統來講,壞處明顯大於好處,所以有必要將數據存儲在數據庫中。

ADO.NET存儲

Quartz使用ADO.NET訪問數據庫,支持的數據庫廠商很是普遍:

  • SqlServer - .NET Framework 2.0的SQL Server驅動程序

  • OracleODP - Oracle的Oracle驅動程序

  • OracleODPManaged - Oracle的Oracle 11託管驅動程序

  • MySql - MySQL Connector / .NET

  • SQLite - SQLite ADO.NET Provider

  • SQLite-Microsoft - Microsoft SQLite ADO.NET Provider

  • Firebird - Firebird ADO.NET提供程序

  • Npgsql - PostgreSQL Npgsql

數據庫的建立語句能夠在Quartz.Net的源碼中找到:https://github.com/quartznet/quartznet/tree/master/database/tables

咱們能夠經過配置文件來配置Quartz使用數據庫存儲:

# job store
quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
quartz.jobStore.dataSource = quartz_store
quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.PostgreSQLDelegate, Quartz
#quartz.jobStore.useProperties = true

quartz.dataSource.quartz_store.connectionString = Server=localhost;Database=quartz_store;userid=quartz_net;password=xxxxxx;Pooling=true;MinPoolSize=1;MaxPoolSize=10;Timeout=15;SslMode=Disable;
quartz.dataSource.quartz_store.provider = Npgsql

負載均衡

負載均衡是實現高可用的一種方式,當任務量變大之後,單臺服務器很難知足須要,使用負載均衡則使得系統具有了橫向擴展的能力,經過部署多個節點來增長處理Job的能力。

Quartz.Net在使用負載均衡時,須要依賴ADO JobStore,意味着你須要使用數據庫持久化數據。而後咱們可使用如下配置完成負載均衡功能:

quartz.jobStore.clustered = true
quartz.scheduler.instanceId = AUTO
  • clustered:集羣的標識

  • instanceId:當前Scheduler實例的ID,每一個示例的ID不能重複,使用AUTO時系統會自動生成ID

當咱們在多臺服務器上運行Scheduler實例時,須要設置服務器的時鐘時間,確保服務器時間是相同的。針對windows服務器,能夠設置從網絡自動同步時間。

經過Routing訪問Quartz實例

經過Routing訪問Quartz實例的功能,爲咱們作系統分離提供了很好的途徑。

咱們能夠經過如下配置實現Quartz的服務器端遠程訪問:

# export this server to remoting context
quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
quartz.scheduler.exporter.port = 555
quartz.scheduler.exporter.bindName = QuartzScheduler
quartz.scheduler.exporter.channelType = tcp
quartz.scheduler.exporter.channelName = httpQuartz

而後咱們在客戶端系統中配置訪問:

quartz.scheduler.proxy = true
quartz.scheduler.proxy.address = tcp://localhost:555/QuartzScheduler

參考資料

  • Quartz.Net官方文檔

  • Github:Quartz.Net源碼

  • Quartz Cron表達式

  • SampleQuartz源碼下載

本文分享自微信公衆號 - DotNet技術平臺(DotNetCore_Moments)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索