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>
作者:舍长
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
原文地址:https://www.cnblogs.com/panchunting/p/3017075.html