zoukankan      html  css  js  c++  java
  • Topshelf结合Quartz.NET实现服务端定时调度任务

    这周接受到一个新的需求:一天内分时间段定时轮询一个第三方WebAPI,并保存第三方WebAPI结果。

    需求分析:分时段、定时开启、定时结束、轮询。主要工作集中在前三个上,轮询其实就是个Http请求,比较好解决。

    技术选型:

      1、最简单的方式:Windows Service、Timer、HttpClient。
      2、B格高点的方式:Topshelf、Quartz.NET、HttpClient。
    之所以选用第二种方式的原因:

      1、Windows Service尝试写了一个,发现附加进程调试确实麻烦,而且以后若是需求变更,还需要重新调试发布Windows Service

      2、Timer需要在项目中建立多个,区分起来着实麻烦

      3、刚好在学习使用Quartz.NET,打算过段时间做个MVC版本的调度任务管理系统

      4、经过查找cnblog发现,使用Topshelf可以用基于Console的模式先编写、调试程序,等调试通过后,用Topshelf命令即可完成Windows Service安装,据说还可以在Linux上通过Mono安装,也算是可以支持跨平台的咯(*^_^*)或许也可以通过制作Docker镜像来实现。

     Show Code:

    1、添加依赖Nuget包:Topshelf、Topshelf.Log4Net、Quartz、Common.Logging、Common.Logging.Core、Common.Logging.Log4Net1211、log4Net

    2、创建ServiceRunner.cs类,继承ServiceControl, ServiceSuspend,这是为了用Topshelf的Start()、Stop()、Continue()、Pause()来分别执行Quartz任务调度的Start()、Shutdown()、ResumeAll()、PauseAll()方法

     1     public class ServiceRunner : ServiceControl, ServiceSuspend
     2     {
     3         private readonly IScheduler scheduler;
     4         public ServiceRunner()
     5         {
     6             scheduler = StdSchedulerFactory.GetDefaultScheduler();
     7         }
     8         public bool Continue(HostControl hostControl)
     9         {
    10             scheduler.ResumeAll();
    11             return true;
    12         }
    13 
    14         public bool Pause(HostControl hostControl)
    15         {
    16             scheduler.PauseAll();
    17             return true;
    18         }
    19 
    20         public bool Start(HostControl hostControl)
    21         {
    22             scheduler.Start();
    23             return true;
    24         }
    25 
    26         public bool Stop(HostControl hostControl)
    27         {
    28             scheduler.Shutdown(false);
    29             return true;
    30         }
    31     }
    View Code

    3、我在这里采用Topshelf的Custom Service模式,在Main()方法中写如下代码

     1 HostFactory.Run(x =>
     2             {
     3                 x.UseLog4Net();
     4                 x.Service<ServiceRunner>();
     5                 x.SetDescription("QuartzDemo服务描述");
     6                 x.SetDisplayName("QuartzDemo服务显示名称");
     7                 x.SetServiceName("QuartzDemo服务名称");
     8 
     9                 x.EnablePauseAndContinue();
    10             });
    View Code

    4、到此为止,建Windows Service的工作算是基本结束,接下来就是重点了,如何用Quartz做一个定时任务。但是这个过程并不难,这里我采用的是Quartz的Cron模式,相比较Simple模式,此种模式通过配置来制定Trigger触发和Job的执行,在我的Windows Service创建好后,无须我再次编译,只需要替换进一个实现IJob接口的动态链接库,并且在Quartz_jobs.xml配置即可,实现IJob的测试代码如下:

     1 public class TestJob : IJob
     2     {
     3         private readonly ILog _log = LogManager.GetLogger(typeof(TestJob));
     4         public void Execute(IJobExecutionContext context)
     5         {
     6             
     7             _log.Info("测试Job,时间:"+ DateTime.Now.ToString("r"));
     8 
     9         }
    10     }
    View Code

    5、准备Quartz.NET的配置文件quartz.config、quartz_jobs.xml,Quartz的Initialize()方法默认从编译输出目录下读取quartz.config文件,并且在quartz.config文件增加quartz.plugin.xml.fileNames 节点写 ~/quartz_jobs.xml,用来配置Trigger和Job的执行

     1 # You can configure your scheduler in either <quartz> configuration section
     2 # or in quartz properties file
     3 # Configuration section has precedence
     4 
     5 quartz.scheduler.instanceName = QuartzTest
     6 
     7 # configure thread pool info
     8 quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
     9 quartz.threadPool.threadCount = 10
    10 quartz.threadPool.threadPriority = Normal
    11 
    12 # job initialization plugin handles our xml reading, without it defaults are used
    13 quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz
    14 quartz.plugin.xml.fileNames = ~/quartz_jobs.xml
    15 
    16 # export this server to remoting context
    17 #quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
    18 #quartz.scheduler.exporter.port = 555
    19 #quartz.scheduler.exporter.bindName = QuartzScheduler
    20 #quartz.scheduler.exporter.channelType = tcp
    21 #quartz.scheduler.exporter.channelName = httpQuartz
    quartz.config
     1 <?xml version="1.0" encoding="UTF-8"?>
     2 
     3 <!-- This file contains job definitions in schema version 2.0 format -->
     4 
     5 <job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
     6 
     7   <processing-directives>
     8     <overwrite-existing-data>true</overwrite-existing-data>
     9   </processing-directives>
    10 
    11   <schedule>
    12 
    13     <!--TestJob测试 任务配置-->
    14     <job>
    15       <name>TestJob</name>
    16       <group>Test</group>
    17       <description>TestJob测试</description>
    18       <job-type>WindowsService.TestJob,WindowsService</job-type>
    19       <durable>true</durable>
    20       <recover>false</recover>
    21     </job>
    22     <trigger>
    23       <cron>
    24         <name>TestJobTrigger</name>
    25         <group>Test</group>
    26         <job-name>TestJob</job-name>
    27         <job-group>Test</job-group>
    28         <!--<start-time>2017-08-03T16:00:00+16:00</start-time>
    29         <end-time>2017-08-03T18:10:00+18:10</end-time>-->
    30         <cron-expression>0/3 * 0-6 * * ?</cron-expression>
    31       </cron>
    32     </trigger>
    33 
    34   </schedule>
    35 </job-scheduling-data>
    quartz_jobs.xml

    6、至此,我们已经基本可以把项目run起来了,但是这只能在console上看到每三秒打印一行“测试Job,时间:***”,并不能确定在以Windows Service时,定时任务调度也能按计划执行,我们还需要将日志输出到文件,需要继续配置log4Net

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3   <configSections>
     4     <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
     5   </configSections>
     6 
     7   <log4net>
     8     <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
     9       <!--日志路径-->
    10       <param name= "File" value= "F:App_Logservicelog"/>
    11       <!--是否是向文件中追加日志-->
    12       <param name= "AppendToFile" value= "true"/>
    13       <!--不加utf-8编码格式,中文字符将显示成乱码-->
    14       <param name="Encoding" value="utf-8" />
    15       <!--log保留天数-->
    16       <param name= "MaxSizeRollBackups" value= "10"/>
    17       <!--日志文件名是否是固定不变的-->
    18       <param name= "StaticLogFileName" value= "false"/>
    19       <!--日志文件名格式为:2008-08-31.log-->
    20       <param name= "DatePattern" value= "yyyy-MM-dd&quot;.read.log&quot;"/>
    21       <!--日志根据日期滚动-->
    22       <param name= "RollingStyle" value= "Date"/>
    23       <layout type="log4net.Layout.PatternLayout">
    24         <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n %loggername" />
    25       </layout>
    26     </appender>
    27 
    28     <!-- 控制台前台显示日志 -->
    29     <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
    30       <mapping>
    31         <level value="ERROR" />
    32         <foreColor value="Red, HighIntensity" />
    33       </mapping>
    34       <mapping>
    35         <level value="Info" />
    36         <foreColor value="Green" />
    37       </mapping>
    38       <layout type="log4net.Layout.PatternLayout">
    39         <conversionPattern value="%n%date{HH:mm:ss,fff} [%-5level] %m" />
    40       </layout>
    41 
    42       <filter type="log4net.Filter.LevelRangeFilter">
    43         <param name="LevelMin" value="Info" />
    44         <param name="LevelMax" value="Fatal" />
    45       </filter>
    46     </appender>
    47 
    48     <root>
    49       <!--(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低) -->
    50       <level value="all" />
    51       <appender-ref ref="ColoredConsoleAppender"/>
    52       <appender-ref ref="RollingLogFileAppender"/>
    53     </root>
    54   </log4net>
    55 </configuration>
    log4net.config

    同时在Main()方法中增加一行读取log4Net配置文件的代码,另:quartz.config、quartz_jobs.xml、log4net.config这三个文件,分别选中→右键属性→复制到输入目录设为:始终复制

     1         static void Main(string[] args)
     2         {
     3             log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.BaseDirectory + "log4net.config"));
     4             HostFactory.Run(x =>
     5             {
     6                 x.UseLog4Net();
     7                 x.Service<ServiceRunner>();
     8                 x.SetDescription("QuartzDemo服务描述");
     9                 x.SetDisplayName("QuartzDemo服务显示名称");
    10                 x.SetServiceName("QuartzDemo服务名称");
    11 
    12                 x.EnablePauseAndContinue();
    13             });
    14         }
    View Code

    最后,我们还需要将Topshelf注册到Windows中,在CMD中打开debug文件夹,Topshelf命令如下,

    安装:WindowsService.exe install
    启动:WindowsService.exe start
    卸载:WindowsService.exe uninstall
    至此,算是基本完成,我接下来只需要在一个继承IJob的类中实现业务代码即可。(*^_^*)

    参考

    Quartz.NET

    官方学习文档:http://www.quartz-scheduler.net/documentation/index.html

    使用实例介绍:http://www.quartz-scheduler.net/documentation/quartz-2.x/quick-start.html

    官方的源代码下载:http://sourceforge.net/projects/quartznet/files/quartznet/ 

    Topself文档:http://topshelf-project.com/

    Log4Net文档:http://logging.apache.org/log4net/

  • 相关阅读:
    Java程序员进阶:Java异常知识点梳理
    JVM内存模型(运行时数据区)
    史前最详细的:Java线程池核心流程解析
    10年大牛给Java 初学者的学习路线建议,如何学习 Java?
    异常处理(面试题)
    115道Java经典面试题(面中率高、全)
    Java异常处理(捕获异常)
    Java异常处理(观察常见异常)
    Java异常处理(异常基本概念)
    学习笔记:Java数组
  • 原文地址:https://www.cnblogs.com/FredLee/p/7295516.html
Copyright © 2011-2022 走看看