1. 程式人生 > >.NET作業排程 Quartz.NET

.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

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 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