Net作业调度(一) -Quartz.Net入门
2014-11-01 13:14 by 蘑菇先生, 13954 阅读, 7 评论, 收藏, 编辑
背景
很多时候,项目需要在不同时刻,执行一个或很多个不同的作业。
Windows执行计划这时并不能很好的满足需求了,迫切需要一个更为强大,方便管理,集群部署的作业调度框架。
介绍
Quartz一个开源的作业调度框架,OpenSymphony的开源项目。Quartz.Net 是Quartz的C#移植版本。
它一些很好的特性:
1:支持集群,作业分组,作业远程管理。
2:自定义精细的时间触发器,使用简单,作业和触发分离。
3:数据库支持,可以寄宿Windows服务,WebSite,winform等。
实战
Quartz框架的一些基础概念解释:
Scheduler 作业调度器。
IJob 作业接口,继承并实现Execute, 编写执行的具体作业逻辑。
JobBuilder 根据设置,生成一个详细作业信息(JobDetail)。
TriggerBuilder 根据规则,生产对应的Trigger
Nuget安装
PM> Install-Package Quartz
下面是简单使用例子,附带详细的注释:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
 | 
static void Main(string[] args)       {           //从工厂中获取一个调度器实例化           IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();           scheduler.Start();       //开启调度器           //==========例子1(简单使用)===========           IJobDetail job1 = JobBuilder.Create<HelloJob>()  //创建一个作业               .WithIdentity("作业名称", "作业组")               .Build();           ITrigger trigger1 = TriggerBuilder.Create()                                       .WithIdentity("触发器名称", "触发器组")                                       .StartNow()                        //现在开始                                       .WithSimpleSchedule(x => x         //触发时间,5秒一次。                                           .WithIntervalInSeconds(5)                                           .RepeatForever())              //不间断重复执行                                       .Build();           scheduler.ScheduleJob(job1, trigger1);      //把作业,触发器加入调度器。           //==========例子2 (执行时 作业数据传递,时间表达式使用)===========           IJobDetail job2= JobBuilder.Create<DumbJob>()                                       .WithIdentity("myJob", "group1")                                       .UsingJobData("jobSays", "Hello World!")                                       .Build();           ITrigger trigger2 = TriggerBuilder.Create()                                       .WithIdentity("mytrigger", "group1")                                       .StartNow()                                       .WithCronSchedule("/5 * * ? * *")    //时间表达式,5秒一次                                            .Build();           scheduler.ScheduleJob(job2, trigger2);                         //scheduler.Shutdown();         //关闭调度器。       } | 
声明要执行的作业,HelloJob:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
/// <summary>   /// 作业   /// </summary>   public class HelloJob : IJob   {       public void Execute(IJobExecutionContext context)       {           Console.WriteLine("作业执行!");       }   } | 
声明要执行的作业,DumbJob:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
 | 
public class DumbJob : IJob    {        /// <summary>        ///  context 可以获取当前Job的各种状态。        /// </summary>        /// <param name="context"></param>        public void Execute(IJobExecutionContext context)        {            JobDataMap dataMap = context.JobDetail.JobDataMap;            string content = dataMap.GetString("jobSays");            Console.WriteLine("作业执行,jobSays:" + content);        }    } | 
其WithCronSchedule("") 拥有强大的Cron时间表达式,正常情况下WithSimpleSchedule(x) 已经满足大部分对日期设置的要求了。
Quartz.Net官方2.X教程 http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/index.html
Quartz.Net开源地址 https://github.com/quartznet/quartznet
Net作业调度(二) -CrystalQuartz远程管理
2014-11-01 18:21 by 蘑菇先生, 6245 阅读, 24 评论, 收藏, 编辑
介绍
上篇已经了解Quartz.NET的基本使用方法了。但如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个Job管理的界面。
本文介绍Quartz.NET如何进行远程job管理,如图:

实战
一:作业服务端
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
 | 
static void Main(string[] args)       {           var properties = new NameValueCollection();           properties["quartz.scheduler.instanceName"] = "RemoteServerSchedulerClient";           // 设置线程池           properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";           properties["quartz.threadPool.threadCount"] = "5";           properties["quartz.threadPool.threadPriority"] = "Normal";           // 远程输出配置           properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";           properties["quartz.scheduler.exporter.port"] = "556";           properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";           properties["quartz.scheduler.exporter.channelType"] = "tcp";           var schedulerFactory = new StdSchedulerFactory(properties);           var scheduler = schedulerFactory.GetScheduler();           var job = JobBuilder.Create<PrintMessageJob>()               .WithIdentity("myJob", "group1")               .Build();           var trigger = TriggerBuilder.Create()               .WithIdentity("myJobTrigger", "group1")               .StartNow()               .WithCronSchedule("/10 * * ? * *")               .Build();           scheduler.ScheduleJob(job, trigger);           scheduler.Start();       } | 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
public class PrintMessageJob : IJob   {       public void Execute(IJobExecutionContext context)       {           Console.WriteLine("Hello!");       }   } | 
启动如下

二:作业远程管理端,无需写任何代码,引用官方程序集,嵌入到已有的web网站。
PM> Install-Package CrystalQuartz.Remote
Webconfig 需要配置的地方
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
<configuration>     <crystalQuartz>        <provider>            <add property="Type" value="CrystalQuartz.Core.SchedulerProviders.RemoteSchedulerProvider, CrystalQuartz.Core" />            <add property="SchedulerHost" value="tcp://127.0.0.1:556/QuartzScheduler" /> <!--TCP监听的地址-->        </provider>    </crystalQuartz><system.webServer>      <!-- Handler拦截处理了,输出作业监控页面-->        <handlers>            <add name="CrystalQuartzPanel" verb="*" path="CrystalQuartzPanel.axd" type="CrystalQuartz.Web.PagesHandler, CrystalQuartz.Web" />        </handlers>    </system.webServer></configuration> | 
Web管理界面

其他
CrystalQuartz 提供基础功能,可以继续在此基础上进行二次开发,另外推荐使用Window服务寄宿,比较方法。
参考资源
张善友 http://www.cnblogs.com/shanyou/archive/2012/01/15/2323011.html
CrystalQuartz开源的地址 https://github.com/guryanovev/CrystalQuartz
Net作业调度(三) — Quartz.Net进阶
介绍
前面介绍Quartz.Net的基本用法,但在实际应用中,往往有更多的特性需求,比如记录job执行的执行历史,发邮件等。
阅读目录
- Quartz.Net插件
 - TriggerListener,JobListener
 - Cron表达式
 - Quartz.Net线程池
 - 总结
 
Quartz.Net插件
Quartz.net 自身提供了一个插件接口(ISchedulerPlugin)用来增加附加功能,看下官方定义:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
public interface ISchedulerPlugin  {      void Initialize(string pluginName, IScheduler sched);     //关闭调度器      void Shutdown();      //插件启动      void Start();  } | 
继承接口,实现自己的插件。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
 | 
public class MyPlugin : ISchedulerPlugin   {       public void  Initialize(string pluginName, IScheduler sched)       {           Console.WriteLine("实例化");       }       public  void Start()       {            Console.WriteLine("启动");       }       public  void Shutdown()       {           Console.WriteLine("关闭");       }   } | 
主函数里面配置要实现的插件,注释部分,一句话搞定。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
 | 
static void Main(string[] args)        {            var properties = new NameValueCollection();           //MyPlugin 自定义名称。    "命名空间.类名,程序名称"            properties["quartz.plugin.MyPlugin.type"] = "QuartzDemo3.MyPlugin,QuartzDemo3";            var schedulerFactory = new StdSchedulerFactory(properties);            var scheduler = schedulerFactory.GetScheduler();            var job = JobBuilder.Create<HelloJob>()                .WithIdentity("myJob", "group1")                .Build();            var trigger = TriggerBuilder.Create()                                .WithIdentity("mytrigger", "group1")                                .WithCronSchedule("/2 * * ? * *")                                .Build();            scheduler.ScheduleJob(job, trigger);            scheduler.Start();            Thread.Sleep(6000);            scheduler.Shutdown(true);            Console.ReadLine();        } | 
运行结果如下:
 
TriggerListener,JobListener
这2个是对触发器和job本身的行为监听器,这样更好方便跟踪Job的状态及运行情况。
ITriggerListener是官方定义的接口,这里我们直接继承实现。
public class MyTriggerListener : ITriggerListener
    {
        private string name;
        public void TriggerComplete(ITrigger trigger, IJobExecutionContext context, SchedulerInstruction triggerInstructionCode)
        {
            Console.WriteLine("job完成时调用");
        }
        public void TriggerFired(ITrigger trigger, IJobExecutionContext context)
        {
            Console.WriteLine("job执行时调用");
        }
        public void TriggerMisfired(ITrigger trigger)
        {
            Console.WriteLine("错过触发时调用(例:线程不够用的情况下)");
        }
        public bool VetoJobExecution(ITrigger trigger, IJobExecutionContext context)
        {
            //Trigger触发后,job执行时调用本方法。true即否决,job后面不执行。
            return false;
        }
        public string Name { get { return name; } set { name = value; } }
    }
主函数添加:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
 | 
//添加监听器到指定的trigger scheduler.ListenerManager.AddTriggerListener(myJobListener, KeyMatcher<TriggerKey>.KeyEquals(new TriggerKey("mytrigger", "group1"))); ////添加监听器到指定分类的所有监听器。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup")); ////添加监听器到指定分类的所有监听器。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"));////添加监听器到指定的2个分组。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.GroupEquals("myJobGroup"), GroupMatcher<TriggerKey>.GroupEquals("myJobGroup2")); ////添加监听器到所有的触发器上。 //scheduler.ListenerManager.AddTriggerListener(myJobListener, GroupMatcher<TriggerKey>.AnyGroup()); scheduler.Start(); | 
JobListener同理,这里不多做描述。
Cron表达式
quartz中的cron表达式和Linux下的很类似,比如 "/5 * * ? * * *" 这样的7位表达式,最后一位年非必选。
表达式从左到右,依此是秒、分、时、月第几天、月、周几、年。下面表格是要遵守的规范:
| 字段名 | 允许的值 | 允许的特殊字符 | 
|---|---|---|
| Seconds | 0-59 | , - * / | 
| Minutes | 0-59 | , - * / | 
| Hours | 0-23 | , - * / | 
| Day of month | 1-31 | , - * ? / L W | 
| Month | 1-12 or JAN-DEC | , - * / | 
| Day of week | 1-7 or SUN-SAT | , - * ? / L # | 
| Year | 空, 1970-2099 | , - * / | 
| 特殊字符 | 解释 | 
| , | 或的意思。例:分钟位 5,10 即第5分钟或10分都触发。 | 
| / | a/b。 a:代表起始时间,b频率时间。 例; 分钟位 3/5, 从第三分钟开始,每5分钟执行一次。 | 
| * | 频率。 即每一次波动。 例;分钟位 * 即表示每分钟 | 
| - | 区间。 例: 分钟位 5-10 即5到10分期间。 | 
| ? | 任意值 。 即每一次波动。只能用在DayofMonth和DayofWeek,二者冲突。指定一个另一个一个要用? | 
| L | 表示最后。 只能用在DayofMonth和DayofWeek,4L即最后一个星期三 | 
| W | 工作日。 表示最后。 只能用在DayofWeek | 
| # | 4#2。 只能用DayofMonth。 某月的第二个星期三 | 
实例介绍
”0 0 10,14,16 * * ?" 每天10点,14点,16点 触发。
"0 0/5 14,18 * * ?" 每天14点或18点中,每5分钟触发 。
"0 4/15 14-18 * * ?" 每天14点到18点期间, 从第四分钟触发,每15分钟一次。
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发。
Quartz.Net线程池
线程池数量设置:
properties["quartz.threadPool.threadCount"] = "5";
这个线程池的设置,是指同时间,调度器能执行Job的最大数量。
quartz是用每个线程跑一个job。上面的设置可以解释是job并发时能执行5个job,剩下的job如果触发时间恰好到了,当前job会进入暂停状态,直到有可用的线程。
如果在指定的时间范围依旧没有可用线程,会触发misfired时间。
quartz 提供了IThreadPool接口,也可以用自定义线程池来实现。
配置如下:
properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
一般来说作业调度很少并发触发大量job,如果有上百个JOB,可在服务器承受范围内适量增加线程数量。
总结
官方有LoggingTriggerHistoryPlugin,LoggingJobHistoryPlugin 已实现的,触发器和job历史记录的插件。
Quartz.Plugin 命名空间下有官方实现的其他一些插件,也可以自己增加扩展。
quartz中监听器还有SchedulerListener,使用方法基本一样。
本文基于自用经验和官方文档代码来写的,部分是直接翻译的。
Quartz.Net官方教程http://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/index.html
Net作业调度(四)—quartz.net持久化和集群
2015-01-18 15:17 by 蘑菇先生, 7462 阅读, 16 评论, 收藏, 编辑
介绍
在实际使用quartz.net中,持久化能保证实例重启后job不丢失、 集群能均衡服务器压力和解决单点问题。
quartz.net在这两方面配置都比较简单。
持久化
quartz.net的持久化,是把job、trigger一些信息存储到数据库里面,以解决内存存储重启丢失。
下载sql脚本
https://github.com/quartznet/quartznet/blob/master/database/tables/tables_sqlServer.sql
创建个数据库,并执行脚本

QRTZ_BLOB_TRIGGERS 以Blob 类型存储的触发器。
QRTZ_CALENDARS 存放日历信息, quartz.net可以指定一个日历时间范围。
QRTZ_CRON_TRIGGERS cron表达式触发器。
QRTZ_JOB_DETAILS job详细信息。
QRTZ_LOCKS 集群实现同步机制的行锁表
QRTZ_SCHEDULER_STATE 实例信息,集群下多使用。
quartz.net 配置
  //===持久化====
            //存储类型
            properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
            //表明前缀
            properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
            //驱动类型
            properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
            //数据源名称
            properties["quartz.jobStore.dataSource"] = "myDS";
            //连接字符串
            properties["quartz.dataSource.myDS.connectionString"] = @"Data Source=(local);Initial Catalog=JobScheduler;User ID=sa;Password=123465";
            //sqlserver版本
            properties["quartz.dataSource.myDS.provider"] = "SqlServer-20";
启动客户端
  var properties = JobsManager.GetProperties();
            var schedulerFactory = new StdSchedulerFactory(properties);
            scheduler = schedulerFactory.GetScheduler();
            scheduler.Start();
            //var job = JobBuilder.Create<MonitorJob>()
            //    .WithIdentity("test", "value")
            //    .Build();
            //var trigger = (ICronTrigger) TriggerBuilder.Create()
            //    .WithIdentity("test", "value")
            //    .WithCronSchedule("0 0/5 * * * ?")
            //    .Build();
            //scheduler.ScheduleJob(job, trigger);
补充
1: 持久化后,job只有添加一次了(数据库已经有了),所以不能再执行端写添加job的行为。这时候需要一个管理工具,动态添加操作。
2: quartz.net 支持sql server、sqlite、mysql、oracle、mongodb(非官方版)。
集群
部署图:
 
如图quartz.net 的集群模式是依赖数据库表的,所以要持久化配置。 集群节点之间是不通信的,这样分布式的架构,很方便进行水平扩展。
1: 除了线程池数量,instanceId可以不同外,各个节点的配置必须是一样的。
2:集群中节点的系统时间一致。
3:多线程、集群中。quartz.net 利用数据库锁来保证job不会重复执行。
源码在DBSemaphore.cs、UpdateLockRowSemaphore.cs、StdRowLockSemaphore.cs
4:集群化后,某节点失效后,剩余的节点能保证job继续执行下去。
实例配置后启动。
   //cluster
            properties["quartz.jobStore.clustered"] = "true";
            properties["quartz.scheduler.instanceId"] = "AUTO";
简单管理界面:

Net作业调度(五)—quartz.net动态添加job设计
2015-01-19 08:42 by 蘑菇先生, 8343 阅读, 35 评论, 收藏, 编辑
介绍
在实际项目使用中quartz.net中,都希望有一个管理界面可以动态添加job,而避免每次都要上线发布。
也看到有园子的同学问过。这里就介绍下实现动态添加job的几种方式, 也是二次开发的核心模块。
阅读目录:
传统方式
继承IJob,实现业务逻辑,添加到scheduler。
public class MonitorJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            //do something
            Console.WriteLine("test");
        }
    }
 //var job = JobBuilder.Create<MonitorJob>()
            //    .WithIdentity("test", "value")
            //    .Build();
            //var trigger = (ICronTrigger) TriggerBuilder.Create()
            //    .WithIdentity("test", "value")
            //    .WithCronSchedule("0 0/5 * * * ?")
            //    .Build();
            //scheduler.ScheduleJob(job, trigger);
也可以使用CrystalQuartz远程管理暂停取消。之前的博客CrystalQuartz远程管理(二)。
框架反射方式
这种方式需要定义一套接口框架。 比如:
  interface IcustomJob
    {
        void Excute(string context);
        void Failed(string error);
        void Complete(string msg);
    }
1:当我们写job时同一实现这个框架接口,类库形式。
2:写完后编译成DLL,上传到我们的作业执行节点。
3:在执行节点中,通过反射拿到DLL的job信息。
4:然后构建quartz的job,添加到scheduler。
这种方式缺点: 耦合性太高,开发量较大。 优点:集中式管理。
系统结构如图:

进程方式
这个方式和windows任务计划类似。
1:使用方编写自己的job,无需实现任何接口,可执行应用程序形式。
2:将程序发送到执行节点,由执行节点起进程调用job程序。
执行节点调用,示例如下:
 public class ConsoleJob:IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            JobDataMap dataMap = context.JobDetail.JobDataMap;
            string content = dataMap.GetString("jobData");
            var jd = new JavaScriptSerializer().Deserialize<ConsoleJobData>(content);
            Process p = new Process();
            p.StartInfo.UseShellExecute = true;
            p.StartInfo.FileName = jd.Path;
            p.StartInfo.Arguments = jd.Parameters;   //空格分割
            p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
            p.Start();
        }
    }
这种方式相对来说: 耦合性中等,执行节点和job相互不关心,没有依赖,开发量较小。
系统结构如图:

URL方式
URL方式和第三种类似,不过调用的不在是执行程序,而是URL。
1: 使用方在网页或服务中,实现业务逻辑。
2: 然后将Url,交给执行节点post或get执行。
执行节点调用,示例如下:
 public class HttpJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            var dataMap = context.JobDetail.JobDataMap;
            var content = dataMap.GetString("jobData");
            var jd = new JavaScriptSerializer().Deserialize<HttpJobData>(content);
            if (jd.Parameters == null)
                jd.Parameters = string.Empty;
            if (jd.Timeout == 0)
                jd.Timeout = 5*60;
            var result = RequestHelper.Post(jd.Url, jd.ContentType, jd.Timeout, jd.Parameters, jd.heads);
        }
    }
这种方式耦合比较低,使用方不需要单独写应用程序了,和平常业务开发一样。
执行节点的职权,仅仅作为一个触发器。
有2点需要注意的是:
1:请求URL时,注意双方约定token加密,防止非执行节点执行调用。
2:使用方,如果有耗时操作,建议异步执行。
系统结构如图:

框架配置方式
1:使用方直接使用quartz.net框架,实现自己的job。从管理方拉取执行节点配置,然后自行管理执行节点。
2:使用方也可以暴露端口给管理方,以实现监控,修改配置。
这种形式,耦合性最低。是把管理方当成一个配置中心。 ps:几乎和传统方式+CrystalQuartz一样了。

通过context.JobDetail.JobDataMap,可以保存job的需要的信息。
本篇介绍主流的几种实现方案,供大家参考使用。
                    
                

                
            
        
浙公网安备 33010602011771号