Net作业调度(五)—quartz.net动态添加job设计
介绍
在实际项目使用中quartz.net中,都希望有一个管理界面可以动态添加job,而避免每次都要上线发布。
也看到有园子的同学问过。这里就介绍下实现动态添加job的几种方式, 也是二次开发的核心模块。
阅读目录:
传统方式
继承IJob,实现业务逻辑,添加到scheduler。
public class MonitorJob : IJob
{ public void Execute(IJobExecutionContext context)
{ //do something
Console.WriteLine(“test”);
}
} //var job = JobBuilder.Create
也可以使用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。
这种方式缺点: 耦合性太高,开发量较大。 优点:集中式管理。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181839415731718.png)
进程方式
这个方式和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
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相互不关心,没有依赖,开发量较小。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181849136677228.png)
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
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:使用方,如果有耗时操作,建议异步执行。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181853563397152.png)
框架配置方式
1:使用方直接使用quartz.net框架,实现自己的job。从管理方拉取执行节点配置,然后自行管理执行节点。
2:使用方也可以暴露端口给管理方,以实现监控,修改配置。
这种形式,耦合性最低。是把管理方当成一个配置中心。 ps:几乎和传统方式+CrystalQuartz一样了。
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181858070583283.png)
通过context.JobDetail.JobDataMap,可以保存job的需要的信息。
本篇介绍主流的几种实现方案,供大家参考使用。
Net作业调度(四)—quartz.net持久化和集群
介绍
在实际使用quartz.net中,持久化能保证实例重启后job不丢失、 集群能均衡服务器压力和解决单点问题。
quartz.net在这两方面配置都比较简单。
持久化
quartz.net的持久化,是把job、trigger一些信息存储到数据库里面,以解决内存存储重启丢失。
下载sql脚本
https://github.com/quartznet/quartznet/blob/master/database/tables/tables\_sqlServer.sql
创建个数据库,并执行脚本
%E2%80%94quartz.net%E6%8C%81%E4%B9%85%E5%8C%96%E5%92%8C%E9%9B%86%E7%BE%A4/181125329017601.png)
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
补充
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”;
简单管理界面:
%E2%80%94quartz.net%E6%8C%81%E4%B9%85%E5%8C%96%E5%92%8C%E9%9B%86%E7%BE%A4/181503535266416.png)
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();
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();
}
运行结果如下:
%20%E2%80%94%20Quartz.Net%E8%BF%9B%E9%98%B6/172151135196757.png)
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
scheduler.ListenerManager.AddTriggerListener(myJobListener, KeyMatcher<TriggerKey>.KeyEquals(``new TriggerKey(``"mytrigger"``, "group1"``)));
////添加监听器到指定分类的所有监听器。
////添加监听器到指定分类的所有监听器。
////添加监听器到指定的2个分组。
////添加监听器到所有的触发器上。
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作业调度(二) -CrystalQuartz远程管理
介绍
上篇已经了解Quartz.NET的基本使用方法了。但如果想方便的知道某个作业执行情况,需要暂停,启动等操作行为,这时候就需要个Job管理的界面。
本文介绍Quartz.NET如何进行远程job管理,如图:
%20-CrystalQuartz%E8%BF%9C%E7%A8%8B%E7%AE%A1%E7%90%86/011755051751192.png)
实战
一:作业服务端
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!"``);
}
}
启动如下
%20-CrystalQuartz%E8%BF%9C%E7%A8%8B%E7%AE%A1%E7%90%86/011801015192327.png)
二:作业远程管理端,无需写任何代码,引用官方程序集,嵌入到已有的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管理界面
%20-CrystalQuartz%E8%BF%9C%E7%A8%8B%E7%AE%A1%E7%90%86/011811269404890.png)
其他
CrystalQuartz 提供基础功能,可以继续在此基础上进行二次开发,另外推荐使用Window服务寄宿,比较方法。
参考资源
张善友 http://www.cnblogs.com/shanyou/archive/2012/01/15/2323011.html
CrystalQuartz开源的地址 https://github.com/guryanovev/CrystalQuartz