.NET作業排程 Quartz.NET
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(); //關閉排程器。 }
宣告要執行的作業,實現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 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