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源创计划”,欢迎正在阅读的你也加入,一块儿分享。