.NET作业调度 Quartz.NET
2016-08-11
Quartz是一个开源的作业调度框架,OpenSymphony的开源项目。Quartz.Net 是Quartz的C#移植版本。
一.特性:
1:支持集群,作业分组,作业远程管理。
2:自定义精细的时间触发器,使用简单,作业和触发分离。
3:数据库支持,可以寄宿Windows服务,WebSite,winform等。
二、基本概念:
Quartz框架的一些基础概念解释:
Scheduler 作业调度器。
IJob 作业接口,继承并实现Execute, 编写执行的具体作业逻辑。
JobBuilder 根据设置,生成一个详细作业信息(JobDetail)。
TriggerBuilder 根据规则,生产对应的Trigger
三、dll:
Quartz.dll
四、简单例子-基本使用 :
static void Main(string[] args) { //从工厂中获取一个调度器实例化 IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler(); scheduler.Start(); //开启调度器 //==========例子1(简单使用)=========== IJobDetail job1 = JobBuilder.Create<HelloJob>() //创建一个作业 .WithIdentity("作业名称", "作业组") .Build(); ITrigger trigger1 = TriggerBuilder.Create() .WithIdentity("触发器名称", "触发器组") .StartNow() //现在开始 .WithSimpleSchedule(x => x //触发时间,5秒一次。 .WithIntervalInSeconds(5) .RepeatForever()) //不间断重复执行 .Build(); scheduler.ScheduleJob(job1, trigger1); //把作业,触发器加入调度器。 //==========例子2 (执行时 作业数据传递,时间表达式使用)=========== IJobDetail job2= JobBuilder.Create<DumbJob>() .WithIdentity("myJob", "group1") .UsingJobData("jobSays", "Hello World!") .Build(); ITrigger trigger2 = TriggerBuilder.Create() .WithIdentity("mytrigger", "group1") .StartNow() .WithCronSchedule("/5 * * ? * *") //时间表达式,5秒一次 .Build(); scheduler.ScheduleJob(job2, trigger2); //scheduler.Shutdown(); //关闭调度器。 }
/// <summary> /// 作业 /// </summary> public class HelloJob : IJob { public void Execute(IJobExecutionContext context) { Console.WriteLine("作业执行!"); } }
public class DumbJob : IJob { /// <summary> /// context 可以获取当前Job的各种状态。 /// </summary> /// <param name="context"></param> public void Execute(IJobExecutionContext context) { JobDataMap dataMap = context.JobDetail.JobDataMap; string content = dataMap.GetString("jobSays"); Console.WriteLine("作业执行,jobSays:" + content); } }
如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个Job管理的界面,作业远程管理端,无需写任何代码,引用官方程序集,嵌入到已有的web网站。
1.相关程序集:
CrystalQuartz.Core.dll
CrystalQuartz.Web.dll
Common.Logging.dll
NVelocity.dll
Quartz.dll
RemoteSchedulerManager.dll
2.webconfig 配置:
<configuration> <crystalQuartz> <provider> <add property="Type" value="CrystalQuartz.Core.SchedulerProviders.RemoteSchedulerProvider, CrystalQuartz.Core" /> <add property="SchedulerHost" value="tcp://127.0.0.1:556/QuartzScheduler" /> <!--TCP监听的地址--> </provider> </crystalQuartz> <system.webServer> <!-- Handler拦截处理了,输出作业监控页面--> <handlers> <add name="CrystalQuartzPanel" verb="*" path="CrystalQuartzPanel.axd" type="CrystalQuartz.Web.PagesHandler, CrystalQuartz.Web" /> </handlers> </system.webServer> </configuration>3.html页面链接设置:
<a style="font-size: 2em;" href="/CrystalQuartzPanel.axd">CrystalQuartz 管理面板</a>4.启动自己的定时任务
5.点击链接即可进入管理界面,并看到自己的作业:
六、Quartz.NET 进阶:
1.Quartz.NET插件-ISchedulerPlugin:
在实际应用中,往往有更多的特性需求,比如记录job执行的执行历史,发邮件等。Quartz.net 自身提供了一个插件接口(ISchedulerPlugin)用来增加附加功能,看下官方定义
public interface ISchedulerPlugin { void Initialize(string pluginName, IScheduler sched); //关闭调度器 void Shutdown(); //插件启动 void Start(); }
public class MyPlugin : ISchedulerPlugin { public void Initialize(string pluginName, IScheduler sched) { Console.WriteLine("实例化"); } public void Start() { Console.WriteLine("启动"); } public void Shutdown() { Console.WriteLine("关闭"); } }主函数里面配置要实现的插件:
static void Main(string[] args) { var properties = new NameValueCollection(); //MyPlugin 自定义名称。 "命名空间.类名,程序名称" properties["quartz.plugin.MyPlugin.type"] = "QuartzDemo3.MyPlugin,QuartzDemo3"; var schedulerFactory = new StdSchedulerFactory(properties); var scheduler = schedulerFactory.GetScheduler(); var job = JobBuilder.Create<HelloJob>() .WithIdentity("myJob", "group1") .Build(); var trigger = TriggerBuilder.Create() .WithIdentity("mytrigger", "group1") .WithCronSchedule("/2 * * ? * *") .Build(); scheduler.ScheduleJob(job, trigger); scheduler.Start(); Thread.Sleep(6000); scheduler.Shutdown(true); Console.ReadLine(); }
2.TriggerListener 和JobListener:
这2个是对触发器和job本身的行为监听器,这样更好方便跟踪Job的状态及运行情况。 通过实现ITriggerListener或IJobListener接口来实现自己的监听器:
public class MyTriggerListener : ITriggerListener { private string name; public void TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode) { Console.WriteLine("job完成时调用"); } public void TriggerFired(ITrigger trigger, IJobExecutionContext context) { Console.WriteLine("job执行时调用"); } public void TriggerMisfired(ITrigger trigger) { Console.WriteLine("错过触发时调用(例:线程不够用的情况下)"); } public bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context) { //Trigger触发后,job执行时调用本方法。true即否决,job后面不执行。 return false; } public string Name { get { return name; } set { name = value; } } }主函数添加:
//添加监听器到指定的trigger scheduler.ListenerManager.AddTriggerListener(myJobListener, KeyMatcher<TriggerKey>.KeyEquals(new TriggerKey("mytrigger", "group1"))); ////添加监听器到指定分类的所有监听器。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup")); ////添加监听器到指定分类的所有监听器。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup")); ////添加监听器到指定的2个分组。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"), GroupMatcher<TriggerKey>.GroupEquals("myJobGroup2")); ////添加监听器到所有的触发器上。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.AnyGroup()); scheduler.Start();
3.Cron表达式:
quartz.NET中的cron表达式和Linux下的很类似,比如 "/5 * * ? * * *" 这样的7位表达式,最后一位年非必选。
表达式从左到右,依此是秒、分、时、月第几天、月、周几、年。下面表格是要遵守的规范:
字段名 | 允许的值 | 允许的特殊字符 |
---|---|---|
Seconds | 0-59 | , - * / |
Minutes | 0-59 | , - * / |
Hours | 0-23 | , - * / |
Day of month | 1-31 | , - * ? / L W |
Month | 1-12 or JAN-DEC | , - * / |
Day of week | 1-7 or SUN-SAT | , - * ? / L # |
Year | 空, 1970-2099 | , - * / |
特殊字符 | 解释 |
, | 或的意思。例:分钟位 5,10 即第5分钟或10分都触发。 |
/ | a/b。 a:代表起始时间,b频率时间。 例; 分钟位 3/5, 从第三分钟开始,每5分钟执行一次。 |
* | 频率。 即每一次波动。 例;分钟位 * 即表示每分钟 |
- | 区间。 例: 分钟位 5-10 即5到10分期间。 |
? | 任意值 。 即每一次波动。只能用在DayofMonth和DayofWeek,二者冲突。指定一个另一个一个要用? |
L | 表示最后。 只能用在DayofMonth和DayofWeek,4L即最后一个星期三 |
W | 工作日。 表示最后。 只能用在DayofWeek |
# | 4#2。 只能用DayofMonth。 某月的第二个星期三 |
”0 0 10,14,16 * * ?" 每天10点,14点,16点 触发。
"0 0/5 14,18 * * ?" 每天14点或18点中,每5分钟触发 。
"0 4/15 14-18 * * ?" 每天14点到18点期间, 从第四分钟触发,每15分钟一次。
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发。
4.Quartz.NET线程池:
线程池数量设置:
properties["quartz.threadPool.threadCount"] = "5";//是指同一时间,调度器能执行Job的最大数量。
这个线程池的设置,是指同时间,调度器能执行Job的最大数量。
quartz是用每个线程跑一个job。上面的设置可以解释是job并发时能执行5个job,剩下的job如果触发时间恰好到了,当前job会进入暂停状态,直到有可用的线程。
如果在指定的时间范围依旧没有可用线程,会触发misfired时间。
quartz 提供了IThreadPool接口,也可以用自定义线程池来实现。
配置如下:
properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";一般来说作业调度很少并发触发大量job,如果有上百个JOB,可在服务器承受范围内适量增加线程数量
七、Quartz.NET持久化-JobStore:
作业一旦被调度,调度器需要记住并且跟踪作业和它们的执行次数。如果你的作业是30分钟后或每30秒调用,这不是很有用。事实上,作业执行需要非常
准确和即时调用在被调度作业上的Execute()方法。Quartz.NET通过一个称之为作业存储(JobStore)的概念来做作业存储和管理。
Quartz.NET提供两种基本作业存储类型。第一种类型叫做RAMJobStore,它利用通常的内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行。Quartz.net缺省使用的就是RAMJobStore。对许多应用来说,这种作业存储已经足够了。
然而,因为调度程序信息是存储在被分配在内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。如果你需要在重新启动之间持久化调度信息,则将需要第二种类型的作业存储。为了修正这个问题,Quartz.NET 提供了 AdoJobStore。顾名思义,作业仓库通过
ADO.NET把所有数据放在数据库中。数据持久性的代价就是性能降低和复杂性的提高。它将所有的数据通过ADO.NET保存到数据库可中。它的配置要比RAMJobStore稍微复杂,同时速度也没有那么快。但是性能的缺陷不是非常差,尤其是如果你在数据库表的主键上建立索引。
AdoJobStore几乎可以在任何数据库上工作,它广泛地使用Oracle, MySQL, MS SQLServer2000,HSQLDB, PostreSQL 以及 DB2。要使用AdoJobStore,首先必须创建一套Quartz使用的数据库表,可以在Quartz的database\tables找到创建库表的SQL脚本。如果没有找到你的
数据库类型的脚本,那么找到一个已有的,修改成为你数据库所需要的。需要注意的一件事情就是所有Quartz库表名都以QRTZ_作为前缀(例如:表"QRTZ_TRIGGERS",及"QRTZ_JOB_DETAIL")。实际上,可以你可以将前缀设置为任何你想要的前缀,只要你告诉AdoJobStore
那个前缀是什么即可(在你的Quartz属性文件中配置)。对于一个数据库中使用多个scheduler实例,那么配置不同的前缀可以创建多套库表,十分有用。(下载SQL脚本)。
配置:
properties["quartz.scheduler.instanceName"] = "TestScheduler"; properties["quartz.scheduler.instanceId"] = "instance_one"; properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz"; properties["quartz.threadPool.threadCount"] = "5"; properties["quartz.threadPool.threadPriority"] = "Normal"; properties["quartz.jobStore.misfireThreshold"] = "60000"; properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz"; properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz"; properties["quartz.jobStore.useProperties"] = "false"; properties["quartz.jobStore.dataSource"] = "default"; properties["quartz.jobStore.tablePrefix"] = "QRTZ_"; properties["quartz.jobStore.clustered"] = "true"; // if running MS SQL Server we need this properties["quartz.jobStore.selectWithLockSQL"] = "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = @lockName"; properties["quartz.dataSource.default.connectionString"] = @"Server=V-LOZHU02;Database=quartz;Trusted_Connection=True;"; properties["quartz.dataSource.default.provider"] = "SqlServer-20";
public virtual void CleanUp(IScheduler inScheduler) { _log.Warn("***** Deleting existing jobs/triggers *****"); // unschedule jobs string[] groups = inScheduler.TriggerGroupNames; for (int i = 0; i < groups.Length; i++) { String[] names = inScheduler.GetTriggerNames(groups[i]); for (int j = 0; j < names.Length; j++) inScheduler.UnscheduleJob(names[j], groups[i]); } // delete jobs groups = inScheduler.JobGroupNames; for (int i = 0; i < groups.Length; i++) { String[] names = inScheduler.GetJobNames(groups[i]); for (int j = 0; j < names.Length; j++) inScheduler.DeleteJob(names[j], groups[i]); } }
source code: https://github.com/zhulongxi2015/Quartz.NETSource
demo: https://github.com/zhulongxi2015/Quart.NETSimpleDemo
http://www.cnblogs.com/lzrabbit/archive/2012/04/14/2446942.html