Quartz.Net 學習隨手記之04 構建Windows Service承載服務


為什么選擇Windows Service作為承載主體?

Quartz.Net只是一個作業調度框架,其承載的主體沒有限制,可以是ConsoleApp, WebForm, WebApp, MVC etc. 但是只有Windows Service是最穩定的,而且支持自啟動

如何選擇合適的Windows Service?

直接使用Quartz.Net自動的Server端,即Windows Service + Topself,優點:無需重復開發、跨平台

安裝自帶的Windows Service

1. 新建目錄

目錄:D:\Developer\QuartzNET\Server,並拷貝如下文件(之所以新建拷貝,是不想影響原有的代碼)

2. 命令安裝

應管理員身份打開CMD,依次輸入如下命令

cd D:\Developer\QuartzNET\Server

D:

Quartz.Server.exe install (對應卸載命令Quartz.Server.exe uninstall)

Win熱鍵+R,輸入services.msc調出service列表,可以看到服務已安裝

輸入以下命令啟動服務(或者手動啟動也可以,對應停止命令sc stop QuartzServer)

創建調度作業

1. 獲得Remote Schedule

var properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "ServerScheduler";

// set remoting expoter
properties["quartz.scheduler.proxy"] = "true";
properties["quartz.scheduler.proxy.address"] = string.Format("tcp://{0}:{1}/{2}", "localhost", "555",
                                                             "QuartzScheduler");

// Get a reference to the scheduler
var sf = new StdSchedulerFactory(properties);

return sf.GetScheduler();

為什么是上面三個屬性,因為Server服務端公布的屬性如下(詳見quartz.config文件)

# You can configure your scheduler in either <quartz>
  configuration section
  # or in quartz properties file
  # Configuration section has precedence

  quartz.scheduler.instanceName = ServerScheduler

  # configure thread pool info
  quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
  quartz.threadPool.threadCount = 10
  quartz.threadPool.threadPriority = Normal

  # job initialization plugin handles our xml reading, without it defaults are used
  quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
  quartz.plugin.xml.fileNames = ~/quartz_jobs.xml

  # export this server to remoting context
  quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
  quartz.scheduler.exporter.port = 555
  quartz.scheduler.exporter.bindName = QuartzScheduler
  quartz.scheduler.exporter.channelType = tcp
  quartz.scheduler.exporter.channelName = httpQuartz

ScheduleJob如下

public void Run()
{
    // Get an instance of the Quartz.Net scheduler
    var schd = GetScheduler();

    // Start the scheduler if its in standby
    if (!schd.IsStarted)
        schd.Start();

    // Define the Job to be scheduled
    var job = JobBuilder.Create<HelloWorldJob>()
        .WithIdentity("WriteHelloToLog", "IT")
        .RequestRecovery()
        .Build();

    // Associate a trigger with the Job
    var trigger = (ICronTrigger)TriggerBuilder.Create()
        .WithIdentity("WriteHelloToLog", "IT")
        .WithCronSchedule("0 0/1 * 1/1 * ? *") // visit http://www.cronmaker.com/ Queues the job every minute
        .StartAt(DateTime.UtcNow)
        .WithPriority(1)
        .Build();

    //schd.DeleteJob(new JobKey("WriteHelloToLog", "IT"));
    // Validate that the job doesn't already exists
    if (!schd.CheckExists(job.Key))
    {
        var schedule = schd.ScheduleJob(job, trigger);
        Console.WriteLine("Job '{0}' scheduled for '{1}'", "WriteHelloToLog", schedule.ToString("r"));
    }
}

具體的HelloWorldJob如下

View Code
class HelloWorldJob : IJob
{

    private static readonly ILog Log = LogManager.GetLogger(typeof(HelloWorldJob));

    /// <summary> 
        /// Empty constructor for job initilization
        /// <para>
        /// Quartz requires a public empty constructor so that the
        /// scheduler can instantiate the class whenever it needs.
        /// </para>
        /// </summary>
    public HelloWorldJob()
        {

        }

    public void Execute(IJobExecutionContext context)
    {
        try
        {
            Log.InfoFormat("{0}****{0}Job {1} fired @ {2} next scheduled for {3}{0}***{0}",
                                                                    Environment.NewLine,
                                                                    context.JobDetail.Key,
                                                                    context.FireTimeUtc.Value.ToString("r"),
                                                                    context.NextFireTimeUtc.Value.ToString("r"));


            Log.InfoFormat("{0}***{0}Hello World!{0}***{0}", Environment.NewLine);
        }
        catch (Exception ex)
        {
            Log.InfoFormat("{0}***{0}Failed: {1}{0}***{0}", Environment.NewLine, ex.Message);
        }
    }
}

控制台

View Code
class Program
{
    static void Main(string[] args)
    {
        try
        {
            // Infinite loop, so that the console doesn't close on you
            while (true)
            {
                var sj = new ScheduledJob();
                sj.Run();

                Console.WriteLine(@"{0}Check Quartz.net\Trace\application.log.txt for Job updates{0}",
                                    Environment.NewLine);

                Console.WriteLine("{0}Press Ctrl^C to close the window. The job will continue " +
                                    "to run via Quartz.Net windows service, " +
                                    "see job activity in the Quartz.Net Trace file...{0}",
                                    Environment.NewLine);

                Thread.Sleep(10000 * 100000);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Failed: {0}", ex.Message);
            Console.ReadKey();
        }
    }
}

編譯無誤后,把編譯后的exe文件拷貝到D:\Developer\QuartzNET\Server目錄下,然后停止並重啟QuartzServer服務。

最后F5本作業,然后按Crtl+C推出,你會看到服務已成功運行,作業也成功執行

釋疑

1. 為什么要重啟服務?

編譯拷貝文件后一定要重啟服務,因為Windows Service如果不重啟的話無法識別新的exe文件或者DLL文件

2. 創建基於此Windows Service的調度作業的關鍵是什么?

調度作業和公布的Remote Schedule要保持一致,即tcp://localhost:555/QuartzScheduler,具體看quartz.config配置

3. 可以保存調度作業到數據庫嗎?

可以,在server端的quartz.config中附加如下配置

  # job store
  quartz.jobStore.misfireThreshold =60000
  quartz.jobStore.type = Quartz.Impl.AdoJobStore.JobStoreTX, Quartz
  quartz.jobStore.driverDelegateType = Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz
  quartz.jobStore.useProperties = true
  quartz.jobStore.dataSource = default
  quartz.jobStore.tablePrefix = QRTZ_
  quartz.dataSource.default.connectionString = Server=10.7.11.114;Database=CCLOG_QuartzNET;User Id=CCLOG_LMS_PRO;Password=Testlms20!!
  quartz.dataSource.default.provider = SqlServer-20

4. 使用log4net記錄日志

更改Server的Quartz.Server.exe.config配置文件如下

View Code
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <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.Log4net1211">
        <arg key="configType" value="INLINE" />
        <!--<arg key="configFile" value="Trace/ApplicationLog.txt" />-->
        <!--<arg key="level" value="INFO" />-->
      </factoryAdapter>
    </logging>
  </common>
  <appSettings>
    <add key="log4net.Internal.Debug" value="false"/>
  </appSettings>
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d [%t] %-5p %l - %m%n" />
      </layout>
    </appender>
    <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%d [%t] %-5p %l - %m%n" />
      </layout>
    </appender>
    <appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
      <param name="File" value="Trace/ApplicationLog.txt" />
      <appendToFile value="true" />

      <!--Make the rolling file name with the date and size-->
      <rollingStyle value="Composite" />
      <datePattern value="yyyyMM" />
      <maxSizeRollBackups value="100" />
      <maximumFileSize value="2MB" />

      <!--Make the rolling file name like this MyQuartzLog201303.txt, or the deault will be MyQuartzLog.txt201303-->
      <PreserveLogFileNameExtension value="true" />
      <staticLogFileName value="false" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n" />
      </layout>
    </appender>
    <root>
      <level value="INFO" />
      <appender-ref ref="ConsoleAppender" />
      <appender-ref ref="EventLogAppender" />
      <appender-ref ref="RollingFileAppender"/>
    </root>
  </log4net>
</configuration>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM