zoukankan      html  css  js  c++  java
  • Topshelf+Quartz3.0基于控制台应用程序快速开发可调度windows服务

    1.TopShelf

    TopShelf是一个开源的跨平台的宿主服务框架。可通过.Net Core/.Net Framwork控制台应用程序快速开发windows服务,更加便于服务调试。

    本文基于.Net Core2.2快速开发windows服务

    首先,我们创建一个控制台应用程序

    然后添加Topshelf Nuget程序包  版本4.2.1

     通过Topshelf集成的Log4net管理日志,所以我们这里添加了Topshelf.LogNet4 Nuget程序包

     添加log4net.config日志配置文件(需手动新建config文件,复制以下内容即可),一般默认配置就可以,主要是改一下日志路径和控制台日志输出级别

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3   <configSections>
     4     <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
     5   </configSections>
     6   <log4net>
     7     <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
     8       <!--日志路径-->
     9       <param name= "File" value= "D:log"/>
    10       <!--是否是向文件中追加日志-->
    11       <param name= "AppendToFile" value= "true"/>
    12       <!--备份文件的最大切分数量-->
    13       <param name= "MaxSizeRollBackups" value= "100"/>
    14       <!-- 每个文件的大小限制  -->
    15       <param name="MaximumFileSize" value="10MB" />
    16       <!-- RollingStyle Composite 综合  Size 按大小  Date 按时间 -->
    17       <param name="RollingStyle" value="Composite" />
    18       <!--最小锁定模式,允许多个进程写入同一个文件-->
    19       <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
    20       <!--日志文件名是否是固定不变的-->
    21       <param name= "StaticLogFileName" value= "false"/>
    22       <!--日志文件名格式为:2008-08-31.log-->
    23       <datePattern value="yyyy-MM-dd\&quot;Log4Net&quot;'.log'" />
    24       <layout type="log4net.Layout.PatternLayout">
    25         <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
    26       </layout>
    27     </appender>
    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="%d [%-5level] %m%n" />
    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="INFO" />
    51       <appender-ref ref="ColoredConsoleAppender"/>
    52       <appender-ref ref="RollingLogFileAppender"/>
    53     </root>
    54   </log4net>
    55 </configuration>

     Topshelf关键点:下面我们开始修改Program文件中程序主函数,如下

     1         //使用Log4进行日志管理
     2         static ILog log = LogManager.GetLogger(typeof(Program));
     3         public static void Main(string[] args)
     4         {
     5             try
     6             {
     7                 //Console.WriteLine("Hello World!");
     8                 HostFactory.Run(x =>
     9                 {
    10                     x.UseLog4Net("log4net.config", true);//使用配置文件
    11                     //指定服务
    12                     x.Service<MyService>(y =>
    13                         {
    14                             y.ConstructUsing<MyService>(service => new MyService());//实际业务逻辑处理的地方
    15                             y.WhenStarted((tc, th) => tc.Start(th));//自定义服务类启动动作,其中th表示自定义服务类实现的ServiceControl接口方法中的入参
    16                             y.WhenStopped((ts, th) => ts.Stop(th));//自定义服务类结束动作
    17                         });
    18 
    19                     x.RunAsLocalSystem();
    20 
    21                     // 服务描述信息
    22                     x.SetDescription(ConfigurationManager.AppSettings["ServiceDescription"]);
    23                     // 服务显示名称
    24                     x.SetDisplayName(ConfigurationManager.AppSettings["ServiceDisplayName"]);
    25                     // 服务名称
    26                     x.SetServiceName(ConfigurationManager.AppSettings["ServiceName"]);
    27                 });
    28             }
    29             catch (Exception ex)
    30             {
    31                 log.Error("服务异常:" + ex.Message);
    32             }
    33         }

    其中服务的一些基本信息(服务名称,描述信息等)我们通过App.config配置文件进行统一管理。添加App.config文件

     1 <?xml version="1.0" encoding="utf-8" ?>
     2 <configuration>
     3   <appSettings>
     4     <!--服务名称-->
     5     <add key="ServiceName" value="QuartzService"/>
     6     <!--服务显示名称-->
     7     <add key="ServiceDisplayName" value="Quartz"/>
     8     <!--服务描述-->
     9     <add key="ServiceDescription" value="Quartz定时服务"/>
    10     <!--操作人-->
    11     <add key="OpUser" value="Quartz"/>
    12   </appSettings>
    13   <connectionStrings>
    14     <!--数据库连接字符串>-->
    15   </connectionStrings>
    16 </configuration>

    主函数中我们指定了MyService自定义功能类,它是实际处理业务逻辑的地方,也是程序功能处理的入口。自定义类实现了ServiceControl接口,该接口一共包含两个需实现的方法:

    bool Start(HostControl hostControl);
    bool Stop(HostControl hostControl);

     1 public class MyService: ServiceControl
     2     {
     3         /// <summary>
     4         /// 程序开始入口
     5         /// </summary>
     6         /// <param name="hostControl"></param>
     7         /// <returns></returns>
     8         public bool Start(HostControl hostControl)
     9         {
    10             //do something you want to do here
    11             return true;
    12         }
    13 
    14         /// <summary>
    15         /// 程序结束
    16         /// </summary>
    17         /// <param name="hostControl"></param>
    18         /// <returns></returns>
    19         public bool Stop(HostControl hostControl)
    20         {
    21             return true;
    22         }
    23     }

    至此,我们通过Topshelf快速开发windows服务的功能已基本完成,同学只需要在Start()方法中添加自己的服务处理功能即可。

    但是,我们这里想介绍下如何结合Quart任务调度框架使用。

    首先简单介绍下Quartz:

    Quartz 是一个开源作业调度框架,允许程序开发人员根据时间的间隔来调度作业,实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。

    我们需要明白 Quartz 的几个核心概念,这样理解起 Quartz 的原理就会变得简单了。

    1. Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:(Quartz3.0版本  方法返类型是Task,老版本为void)
      Task Execute(IJobExecutionContext context);
    2. JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。 
    3. Trigger 代表一个调度参数的配置,什么时候去调。 
    4. Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。 

    好了,了解了上面几个概念之后,我们准备开始入手。本文以配置文件的方式进行作业调度管理。

    第一步,添加Quartz3.0 Nuget程序包

     

    第二步,修改我们上面添加的自定义服务类,如下:

    主要做如下调整:

    1.声明并初始化Quartz调度程序实例,其中scheduler初始化方式和老版本的有点不同,Quartz3.0版本的返回值是Task,我们通过方式1和方式2都可以实现(实际使用中,选择一种即可) 

    2.程序开始和结束方法,调用scheduler的Start()和Shutdown()

     1         private IScheduler scheduler;//声明Quartz调度程序实例,用与管理Job
     2         /// <summary>
     3         /// 构造函数初始化IScheduler实例
     4         /// </summary>
     5         public MyService()
     6         {
     7             //Quartz3.0版本初始化方式1
     8             scheduler = StdSchedulerFactory.GetDefaultScheduler().GetAwaiter().GetResult();
     9             //Quartz3.0版本初始化方式2
    10             //scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
    11         }
    12 
    13         /// <summary>
    14         /// 程序开始入口
    15         /// </summary>
    16         /// <param name="hostControl"></param>
    17         /// <returns></returns>
    18         public bool Start(HostControl hostControl)
    19         {
    20             scheduler.Start();
    21             return true;
    22         }
    23 
    24         /// <summary>
    25         /// 程序结束
    26         /// </summary>
    27         /// <param name="hostControl"></param>
    28         /// <returns></returns>
    29         public bool Stop(HostControl hostControl)
    30         {
    31             scheduler.Shutdown();
    32             return true;
    33         }

    第三步,我们需要添加工作任务job,这里是实际干活的任务。新建一个功能类,实现接口IJob

     1 /// <summary>
     2     /// 自定义job,实际功能处理单元,需实现IJob
     3     /// </summary>
     4     public class TestJob : IJob
     5     {
     6         log4net.ILog _logger = log4net.LogManager.GetLogger(typeof(TestJob));
     7         public Task Execute(IJobExecutionContext context)
     8         {
     9             _logger.InfoFormat("TestJob测试");
    10             //return Task.FromResult("TestJob测试");
    11             return Task.Factory.StartNew(() => Console.WriteLine($"工作任务测试:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"));
    12         }
    13     }

    最后,也是比较重要的地方。我们需添加配置文件,来关联我们上面创建的schedule和job。

    添加配置文件有个注意的地方:文件属性要选择始终复制,便于发布版本时可用。

    添加quartz.config

    默认配置就可以,里面指定的调度程序实例,线程池数量,配置文件路径名称等

     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 = ServerScheduler
     6 
     7 # configure thread pool info
     8 quartz.threadPool.type = Quartz.Simpl.SimpleThreadPool, Quartz
     9 quartz.threadPool.threadCount = 50
    10 
    11 # job initialization plugin handles our xml reading, without it defaults are used
    12 quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz.Plugins
    13 quartz.plugin.xml.fileNames = ~/quartz_jobs.xml
    14 
    15 # export this server to remoting context
    16 quartz.scheduler.exporter.type = Quartz.Simpl.RemotingSchedulerExporter, Quartz
    17 quartz.scheduler.exporter.port = 555
    18 quartz.scheduler.exporter.bindName = QuartzScheduler
    19 quartz.scheduler.exporter.channelType = tcp
    20 quartz.scheduler.exporter.channelName = httpQuartz

     添加quartz_jobs.xml

    该配置文件才是体现精华的地方。这里面真正实现了schedule,job和trigger三者的联系。也是好多同学上面有疑问的地方(为什么schedule.start()之后会自动调用我创建的job)

    配置注意点:

    1.job->job-type,配置job类和job类所在的命名空间

    2.trigger->job-name/job-group 一定要和你上面创建的job的name/group一样

     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     <job>
    14       <name>sampleJob</name>
    15       <group>sampleGroup</group>
    16       <description>Sample job for Quartz Server</description>
    17       <job-type>QuartzServer.TestJob, QuartzServer</job-type>
    18       <durable>true</durable>
    19       <recover>false</recover>
    20       <!--<job-data-map>
    21         <entry>
    22           <key>key1</key>
    23           <value>value1</value>
    24         </entry>
    25         <entry>
    26           <key>key2</key>
    27           <value>value2</value>
    28         </entry>
    29       </job-data-map>-->
    30     </job>
    31     <!--当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择-->
    32     <!--<trigger>
    33       <simple>
    34         <name>sampleSimpleTrigger</name>
    35         <group>sampleSimpleGroup</group>
    36         <description>Simple trigger to simply fire sample job</description>
    37         <job-name>sampleJob</job-name>
    38         <job-group>sampleGroup</job-group>
    39         <misfire-instruction>SmartPolicy</misfire-instruction>
    40         <repeat-count>-1</repeat-count>
    41         <repeat-interval>10000</repeat-interval>
    42       </simple>
    43     </trigger>-->
    44     <!--通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等-->
    45     <trigger>
    46       <cron>
    47         <name>sampleCronTrigger</name>
    48         <group>sampleCronGroup</group>
    49         <description>Cron trigger to simply fire sample job</description>
    50         <job-name>sampleJob</job-name>
    51         <job-group>sampleGroup</job-group>
    52         <misfire-instruction>SmartPolicy</misfire-instruction>
    53         <cron-expression>0/10 * * * * ?</cron-expression>
    54       </cron>
    55     </trigger>
    56     <!--quartz.Calendar它是一些日历特定时间点的集合,一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。
    57     假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除-->
    58     <!--<trigger>
    59       <calendar-interval>
    60         <name>sampleCalendarIntervalTrigger</name>
    61         <group>sampleCalendarIntervalGroup</group>
    62         <description>Calendar interval trigger to simply fire sample job</description>
    63         <job-name>sampleJob</job-name>
    64         <job-group>sampleGroup</job-group>
    65         <misfire-instruction>SmartPolicy</misfire-instruction>
    66         <repeat-interval>15</repeat-interval>
    67         <repeat-interval-unit>Second</repeat-interval-unit>
    68       </calendar-interval>
    69     </trigger>-->
    70   </schedule>
    71 </job-scheduling-data>

     常用的cron-trigger表达式配置说明

     1 cron expressions 整体上还是非常容易理解的,只有一点需要注意:"?"号的用法,看下文可以知道“?”可以用在 day of month 和 day of week中,他主要是为了解决如下场景,如:每月的1号的每小时的31分钟,正确的表达式是:* 31 * 1 * ?,而不能是:* 31 * 1 * *,因为这样代表每周的任意一天。
     2 
     3 由7段构成:秒 分 时 日 月 星期 年(可选)
     4 "-" :表示范围  MON-WED表示星期一到星期三
     5 "," :表示列举 MON,WEB表示星期一和星期三
     6 "*" :表是“每”,每月,每天,每周,每年等
     7 "/" :表示增量:0/15(处于分钟段里面) 每15分钟,在0分以后开始,3/20 每20分钟,从3分钟以后开始
     8 "?" :只能出现在日,星期段里面,表示不指定具体的值
     9 "L" :只能出现在日,星期段里面,是Last的缩写,一个月的最后一天,一个星期的最后一天(星期六)
    10 "W" :表示工作日,距离给定值最近的工作日
    11 "#" :表示一个月的第几个星期几,例如:"6#3"表示每个月的第三个星期五(1=SUN...6=FRI,7=SAT)
    12 
    13 官方实例
    14 Expression    Meaning
    15 0 0 12 * * ?    每天中午12点触发
    16 0 15 10 ? * *    每天上午10:15触发
    17 0 15 10 * * ?    每天上午10:15触发
    18 0 15 10 * * ? *    每天上午10:15触发
    19 0 15 10 * * ? 2005    2005年的每天上午10:15触发
    20 0 * 14 * * ?    在每天下午2点到下午2:59期间的每1分钟触发
    21 0 0/5 14 * * ?    在每天下午2点到下午2:55期间的每5分钟触发
    22 0 0/5 14,18 * * ?    在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
    23 0 0-5 14 * * ?    在每天下午2点到下午2:05期间的每1分钟触发
    24 0 10,44 14 ? 3 WED    每年三月的星期三的下午2:10和2:44触发
    25 0 15 10 ? * MON-FRI    周一至周五的上午10:15触发
    26 0 15 10 15 * ?    每月15日上午10:15触发
    27 0 15 10 L * ?    每月最后一日的上午10:15触发
    28 0 15 10 L-2 * ?    Fire at 10:15am on the 2nd-to-last last day of every month
    29 0 15 10 ? * 6L    每月的最后一个星期五上午10:15触发
    30 0 15 10 ? * 6L    Fire at 10:15am on the last Friday of every month
    31 0 15 10 ? * 6L 2002-2005    2002年至2005年的每月的最后一个星期五上午10:15触发
    32 0 15 10 ? * 6#3    每月的第三个星期五上午10:15触发
    33 0 0 12 1/5 * ?    Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.
    34 0 11 11 11 11 ?    Fire every November 11th at 11:11am.

     好了,大功告成,我们只需要发布部署即可。

    安装:TopshelfDemo.exe install
    启动:TopshelfDemo.exe start
    卸载:TopshelfDemo.exe uninstall
     
    安装程序(指定项目发布文件地址 进行Install)

    在服务列表中可看到我们刚才安装的windows服务

     

     好了,本文关于介绍topshelf框架快速开发windows服务以及通过Quartz框架调度管理服务的开发已介绍完毕,文中有不足之处,请各位看官多多指正!

  • 相关阅读:
    数制
    转移指令检测题9
    转移指令笔记(1)
    汇编笔记
    汇编语言学习笔记
    C++中的虚函数
    windows程序设计(四)
    windows程序设计(三)
    windows程序设计(二)
    通过Url网络编程实现下载
  • 原文地址:https://www.cnblogs.com/chenxf1117/p/12551633.html
Copyright © 2011-2022 走看看