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框架調度管理服務的開發已介紹完畢,文中有不足之處,請各位看官多多指正!


免責聲明!

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



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