.NET作业调度 Quartz.NET

2017-01-13 10:50:32来源:csdn作者:zhulongxi人点击


Quartz是一个开源的作业调度框架,OpenSymphony的开源项目。Quartz.Net是Quartz的C#移植版本。


一.特性:


1:支持集群,作业分组,作业远程管理。


2:自定义精细的时间触发器,使用简单,作业和触发分离。


3:数据库支持,可以寄宿Windows服务,WebSite,winform等。


二、基本概念:

Quartz框架的一些基础概念解释:


  Scheduler 作业调度器。


  IJob 作业接口,继承并实现Execute,编写执行的具体作业逻辑。


  JobBuilder根据设置,生成一个详细作业信息(JobDetail)。


  TriggerBuilder根据规则,生产对应的Trigger


三、dll:


Quartz.dll


Common.Logging.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(); //关闭调度器。
}声明要执行的作业,实现IJob接口的execute方法:
/// <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);
}
} 五、Quartz.NET 的CrystalQuartz远程管理:

如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个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 voidInitialize(string pluginName, IScheduler sched)
{
Console.WriteLine("实例化");
}
publicvoid Start()
{
Console.WriteLine("启动");
}
publicvoid 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();JobListener同理。

3.Cron表达式:


quartz.NET中的cron表达式和Linux下的很类似,比如"/5 * * ? * * *" 这样的7位表达式,最后一位年非必选。


表达式从左到右,依此是秒、分、时、月第几天、月、周几、年。下面表格是要遵守的规范:












































字段名允许的值允许的特殊字符
Seconds0-59, - * /
Minutes0-59, - * /
Hours0-23, - * /
Day of month1-31, - * ? / L W
Month1-12 or JAN-DEC, - * /
Day of week1-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"; 持久化后,job只有添加一次了(数据库已经有了),所以不能再执行端写添加job的行为,可以在执行job前先清空数据库中的job防止重复添加报异常:
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]);
}
}先从初始化 SchedulerFactory 和Scheduler开始。然后,不再需要初始化作业和触发器,而是要获取触发器群组名称列表,之后对于每个群组名称,获取触发器名称列表。请注意,每个现有的作业都应当用Scheduler. RescheduleJob ()方法重新调度。仅仅重新初始化在先前的应用程序运行时终止的作业,不会正确地装载触发器的属性。


8. 将Quartz.NET 服务创建到window services中:https://github.com/zhulongxi2015/Quartz.NETProject/tree/master


8.1.首先创建Windows服务程序(windows service):QuartzArchitecture.Service


引用程序集:Quartz.dll, log4net.dll,Common.Logging.Log4Net.dll(不能少,没有它的话在创建服务的时候回有1053错误码),Common.Logging.dll


此时自动生成一个ProjectInstaller.cs服务组件类,将serviceInstaller1的StartType设为Automatic,serviceProcessInstaller1的Account设置为LocalSystem.


新建一个自己的服务组件类:MyQuartzService1.cs,继承自ServiceBase类,在里面分别重写OnStart,OnStop,OnPause,OnContinue方法。在构造函数中实例化scheduler..


在app.confg中配置log4net ,common, quartz节点:


<configSections>
<section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"/><section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/><sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
</sectionGroup></configSections> <common>
<logging>
<factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">
<arg key="configType" value="INLINE"/>
<arg key="level" value="ALL" />
<arg key="showLogName" value="true" />
<arg key="showDataTime" value="true" />
<arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
</factoryAdapter>
</logging>
</common><log4net>
<appender name="InfoFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log/" />
<appendToFile value="true" />
<param name="DatePattern" value="yyyyMMdd".txt"" />
<rollingStyle value="Date" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="1024KB" />
<staticLogFileName value="false" />
<Encoding value="UTF-8" />
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger- %message%newline" />
</layout>
</appender>
<appender name="ErrorFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log/error.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="100" />
<maximumFileSize value="10240KB" />
<staticLogFileName value="true" />
<Encoding value="UTF-8" />
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="WARN" />
<param name="LevelMax" value="FATAL" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="InfoFileAppender" />
<appender-ref ref="ErrorFileAppender" />
</root>
</log4net><quartz>
<add key="quartz.scheduler.instanceName" value="ServerScheduler" /><add key="quartz.threadPool.type" value="Quartz.Simpl.SimpleThreadPool, Quartz" />
<add key="quartz.threadPool.threadCount" value="10" />
<add key="quartz.threadPool.threadPriority" value="2" /><add key="quartz.jobStore.misfireThreshold" value="60000" />
<add key="quartz.jobStore.type" value="Quartz.Simpl.RAMJobStore, Quartz" />
</quartz>


8.2.创建Job类库:MyQuartzArchitecture.Jobs


在里面添加自己的job:(实现IJob接口)



8.3.创建Runner类库,表示使用哪种存储(adostore/simple):QuartzRunner



8.4 编译服务QuartzRunner,将bin中的文件拷到磁盘下面,(如d:/service),


通过cmd: sc create servicename binpath=d:/svervice/QuartzArchitecture.Service.exe 创建服务。


net start servicename 启用服务


net stop servicename 停止服务




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



最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台