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" target="_blank" rel="external nofollow" >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();
JobListener同理。
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";
持久化後,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