Quartz.net 在應用程序中統一調度各任務,Config文件配置所有的任務項

         偶然發現項目中有多個需要定時完成的任務,通過以下方式進行的。

  • 有一些是寫在MSSql Server的作業中,這些是純粹滴操作數據庫的行爲。
  • 有一些是寫在Window Service中,用Timer定時器來輪詢完成。既包括操作數據庫,又包括髮郵件,或者同步數據去其他系統。
         但是這些都1對1的,在SqlServer中,一個作業完成一件事件。有多個作業那就必須建立多個作業。需要移植到其他數據庫就必須把每個作業的Sql複製過去執行,很繁瑣。
         在Window Service中,也是1個任務對於1個服務,若有多個任務就建立多個服務。若要移植到其他機器,就得一個一個去安裝這些服務。
         最重要的是,你都不清楚到底有多少Window Service是這個項目的,有多少作業在MSSql Server中運行。管理相當不方便。

         在可移植性方面,確實非常不方便。也不便於統一管理。頭疼。

          最近研究Quartz.net,發現它能很方便地統一調度多個任務。若是Web的項目,只需在Application_Start方法中加載這些任務,並開始調度。在Application_Stop方法中將調度Shutdown。若有更改任務,只需重啓應用程序即可。
          我進行了試驗,將所有的任務放在配置文件中JobScheduler.config:
<?xml version="1.0" encoding="utf-8" ?>
<JobScheduler>
  <Job Description="作業1">
    <DllName>JobLibrary.dll</DllName>
    <JobDetail job="test1" group="test1Group" jobtype="JobLibrary.FirstJob" />
    <Trigger name="test1" group="test1Group" type="CronTrigger" expression="0/2 * * * * ?" />
  </Job>
  <Job Description="作業2">
    <DllName>JobLibrary.dll</DllName>
    <JobDetail job="test2" group="test2Group" jobtype="JobLibrary.SecondJob" />
    <Trigger name="test2" group="test2Group" type="CronTrigger" expression="0/5 * * * * ?" />
  </Job>
  <Job Description="作業3">
    <DllName>JobLibrary.dll</DllName>
    <JobDetail job="test3" group="test3Group" jobtype="JobLibrary.ThirdJob" />
    <Trigger name="test3" group="test3Group" type="CronTrigger" expression="0/3 * * * * ?" />
  </Job>
</JobScheduler>

                 統一調度的主程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Quartz.Impl;
using Quartz;
using Quartz.Impl.Triggers;
using System.Reflection;
using System.IO;
using Common.Logging;

namespace JobLibrary
{
    public class JobManage
    {
        private static ISchedulerFactory sf = new StdSchedulerFactory();
        private static IScheduler scheduler;
        static readonly ILog errorLog = LogManager.GetLogger("LogFileAppender");
        public static void StartScheduleFromConfig()
        {
            string currentDir = AppDomain.CurrentDomain.BaseDirectory;
            try
            {
                XDocument xDoc = XDocument.Load(Path.Combine(currentDir, "JobScheduler.config"));
                var jobScheduler = from x in xDoc.Descendants("JobScheduler") select x;

                var jobs = jobScheduler.Elements("Job");
                XElement jobDetailXElement, triggerXElement;

                scheduler = sf.GetScheduler();

                CronTriggerImpl cronTrigger;

                foreach (var job in jobs)
                {
                    //加載程序集joblibaray
                    Assembly ass = Assembly.LoadFrom(Path.Combine(currentDir, job.Element("DllName").Value));

                    jobDetailXElement = job.Element("JobDetail");
                    triggerXElement = job.Element("Trigger");

                    JobDetailImpl jobDetail = new JobDetailImpl(jobDetailXElement.Attribute("job").Value,
                                                            jobDetailXElement.Attribute("group").Value,
                                                            ass.GetType(jobDetailXElement.Attribute("jobtype").Value));

                    if (triggerXElement.Attribute("type").Value.Equals("CronTrigger"))
                    {
                        cronTrigger = new CronTriggerImpl(triggerXElement.Attribute("name").Value,
                                                        triggerXElement.Attribute("group").Value,
                                                        triggerXElement.Attribute("expression").Value);
                        scheduler.ScheduleJob(jobDetail, cronTrigger);
                    }
                }
                scheduler.Start();
            }
            catch (Exception e)
            {
                errorLog.Error(e.StackTrace);
            }
        }

        public static void ShutDown()
        {
            if (scheduler != null && !scheduler.IsShutdown)
            {
                scheduler.Shutdown();
            }
        }


        /**  
         * 從Scheduler 移除當前的Job,修改Trigger  
         *   
         * @param jobDetail  
         * @param time  
         * @throws SchedulerException  
         * @throws ParseException  
         */
        public static void ModifyJobTime(IJobExecutionContext jobExecution, String time)
        {

            scheduler = jobExecution.Scheduler;
            ITrigger trigger = jobExecution.Trigger;
            IJobDetail jobDetail = jobExecution.JobDetail;
            if (trigger != null)
            {
                CronTriggerImpl ct = (CronTriggerImpl)trigger;
                // 移除當前進程的Job   
                scheduler.DeleteJob(jobDetail.Key);
                // 修改Trigger   
                ct.CronExpressionString = time;
                Console.WriteLine("CronTrigger getName " + ct.JobName);
                // 重新調度jobDetail   
                scheduler.ScheduleJob(jobDetail, ct);
            }
        }
    }
}

                     以下是3個Job:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Logging;

namespace JobLibrary
{
    public abstract class JobBase
    {
        /// <summary>
        /// JOB狀態日誌
        /// </summary>
        protected internal static readonly ILog jobStatus = LogManager.GetLogger("LogFileAppender");

        /// <summary>
        /// 服務錯誤日誌
        /// </summary>
        protected internal static readonly ILog serviceErrorLog = LogManager.GetLogger("LogFileAppender");
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;


namespace JobLibrary
{
    public class FirstJob : JobBase, IJob
    {

        #region IJob 成員

        public void Execute(IJobExecutionContext context)
        {
            jobStatus.Info("--------1111first job start ----------");
            try
            {
                jobTest();
                
            }
            catch (Exception e)
            {
                serviceErrorLog.Info(string.Concat("first job:", e.StackTrace));
            }
            jobStatus.Info("---------first job Complete ----------");
         }
        #endregion


        public void jobTest()
        {
            jobStatus.Info("weeksetting test1!");
        }

    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;


namespace JobLibrary
{
    public class SecondJob:JobBase,IJob
    {

        #region IJob 成員

        public void Execute(IJobExecutionContext context)
        {
            jobStatus.Info("------------2222 second job start -----------");
            try
            {
                jobTest();
            }
            catch (Exception e)
            {
                serviceErrorLog.Info(String.Concat("second job:", e.StackTrace));
            }
            jobStatus.Info("------------ second job Complete  -----------");
           
        }

        #endregion

        public void jobTest()
        {
            jobStatus.Info("weeksetting test2!");
        }
    }

}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;

namespace JobLibrary
{
    public class ThirdJob : JobBase, IJob
    {
        private static int a = 1;
        #region IJob 成員

        public void Execute(IJobExecutionContext context)
        {
            jobStatus.Info("--------3333Third job start ----------");
            try
            {
                
                if (!context.JobDetail.JobDataMap.Contains("a"))
                {
                    context.JobDetail.JobDataMap.Add("a", a);
                }
                else
                {
                    context.JobDetail.JobDataMap["a"] = a;
                }
                jobTest();

                jobStatus.Info("a=" + context.JobDetail.JobDataMap["a"]);
                if (a == 3)
                {
                    JobManage.ModifyJobTime(context, "0/5 * * * * ?");
                }
                jobStatus.Info("a=" + a);
                a++;
                
            }
            catch (Exception e)
            {
                serviceErrorLog.Info(string.Concat("Third job:", e.StackTrace));
            }
            jobStatus.Info("---------Third job Complete ----------");
         }
        #endregion


        public void jobTest()
        {
            jobStatus.Info("weeksetting test3!");
        }
   
    }
}

              以下是2個配置文件App.config,log4net.config,用於配置Log的:

App.config

<?xml version="1.0"?>
<configuration>
  <configSections>
      <sectionGroup name="common">
        <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging"/>
      </sectionGroup>
  </configSections>
  <common>
    <logging>
      <!--1.此Adapter只輸出到控制檯--><!--
      <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging">
        <arg key="level" value="INFO" />
        <arg key="showLogName" value="true" />
        <arg key="showDataTime" value="true" />
        <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
      </factoryAdapter>-->
      <!--2.此Adapter只輸出到Log4.net的配置文件所指定的地方-->
      <factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4net">
        <arg key="configType" value="FILE"/>
         FILE,FILE-WATCH,INLINE,EXTERNAL
        <arg key="configFile" value="~/log4net.config"/>   <!-- 指定log4net的配置文件名稱 -->
        <arg key="level" value="Warn"/>
      </factoryAdapter>
    </logging>
  </common>
	<runtime>
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
			<dependentAssembly>
				<assemblyIdentity name="Common.Logging" publicKeyToken="AF08829B84F0328E" culture="neutral"/>
				<bindingRedirect oldVersion="0.0.0.0-2.1.2.0" newVersion="2.1.2.0"/>
			</dependentAssembly>
		</assemblyBinding>
	</runtime>
</configuration>

log4net.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <appSettings>
  </appSettings>
  <log4net>
    <!--定義輸出到文件中-->
    <appender name="LogFileAppender"  type="log4net.Appender.RollingFileAppender">
      <!--輸出日誌文件的路徑-->
      <file value="Log\XXLog.log" />
      <!--輸出日誌時自動向後追加-->
      <appendToFile value="true" />
      <!--防止多線程時不能寫Log,官方說線程非安全,但實際使用時,本地測試正常,部署後有不能寫日誌的情況-->
      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
      <!--置爲true,當前最新日誌文件名永遠爲file節中的名字-->
      <staticLogFileName value="false" />
      <!--日誌以大小作爲備份樣式,還有一種方式是Date(日期)-->
      <rollingStyle value="size" />
      <countDirection value="-1" />
      <!--單個日誌的最大容量,(可用的單位:KB|MB|GB)不要使用小數,否則會一直寫入當前日誌-->
      <maximumFileSize value="1MB" />
      <!--日誌最大個數,都是最新的-->
      <maxSizeRollBackups value="10" />
      <datePattern value='"."yyyy-MM-dd".log"' />
      <layout type="log4net.Layout.PatternLayout">
        <!--每條日誌末尾的文字說明-->
        <footer value="**************************************************************" />
        <!--輸出格式-->
        <!--樣例:2008-03-26 13:42:32,111 [10] INFO  Log4NetDemo.MainClass  - info-->
        <conversionPattern  value="%newline%d{yyyy/MM/dd,HH:mm:ss.fff},[%-5level]%newline Message:%message%newline" />
      </layout>
    </appender>
    <root>
      <!--文件形式記錄日誌-->
      <appender-ref ref="LogFileAppender" />
    </root>
  </log4net>
</configuration>

             使用時,在Global.asax.cs中增加這樣:

 protected void Application_Start(object sender, EventArgs e)
        {
           JobManage.StartScheduleFromConfig();
         }
protected void Application_End(object sender, EventArgs e)
        {
                          JobManage.Shutdown(); 
        }

               如有新的任務加入,只需在JobScheduler.config中配置新任務項。若要停止某個任務,只需註釋掉任務項,然後重啓應用程序

               

application改變的條件:

1. iis重啓

2. 代碼改變

3. 應用程序池回收時間到了

4. 應用程序池空閒時間到了

符合以上條件後,第一個request過來時會調用Application_Start。其中3和4是可以配置的(應用程序池屬性 -> 性能)


把所有的Job、JobManage主調度程序都放到一個工程JobLibrary,這樣便於管理,一個Job對於一個類。要移植時,只需把該類庫搬走。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章