前言:Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。
笔者从业于通信行业,整天泡在数据堆里,很多都是繁琐的周期性的低技术水平的数据提取及处理工作。而所需的数据大部分都来自全省统一管理的生产库数据,数据提取耗费大量的人力。于是想通过在远端的堡垒机部署crontab+本地部署任务计划来实现自动的数据提取及推送。Crontab的实现非常容易,麻烦就在于本地没有像crontab这样方便强大的工具。前期笔者一般采用windows计划+批处理文件,Msqql存储过程+作业,程序定时执行任务等3种方式。然而以上三种方式各有利弊,一旦任务计划多起来后,可维护性、可迁移性、安全性上都存在头疼的问题。于是前几个月着手研究任务计划管理方面的资料,恰好.Net领域有个非常不错的开源的作业调度框架,而且博客园里有位张善友前辈攒写了不少关于Quartz.NET课程。于是笔者就开始研究改造Quartz.NET,以期搭建出一个类似MSSql2005作业管理功能之类的企业级的统一任务调度中心。
功夫不负有心人,经过断断续续的研究,目前统一任务调度中心已初具模型。先上图,以飨各位看官。稍后再慢慢跟各位讲诉改造的过程。
图一:任务管理
图二:任务步骤
图三:任务计划
图四:任务通知
目前计划基本上实现了Mssql2005的作业计划功能(功能稍弱一些,优势在于支持crontab方式),任务步骤则只实现了执行SQL语句(支持多数据库),FTP上传下载功能。Quartz.Net是基于数据库+Winservice方式运行的。
下面谈谈笔者在研究过程中遇到的问题:
问题1:Quartz.net的作业信息如何以数据库方式存储的问题。
Quartz.net的介绍资料不多,特别是以数据库方式运行的。后来认真研究了Quartz.net源码的例子,才得以顺利解决。代码如下(由于不知如何配置配置文件,故采用例子中的方式在程序中设置相应的配置变量):
protected override void OnStart(string[] args)
{
logger.Info("启动服务,时间: " + DateTime.Now.ToString());
StartJobs();
}
private void StartJobs()
{
NameValueCollection properties = new NameValueCollection();
properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
RunAdoJobStoreTest("SqlServer-20", "SQLServer", properties);
}
private void RunAdoJobStoreTest(string dbProvider, string connectionStringId,
NameValueCollection extraProperties)
{
NameValueCollection properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = "TestScheduler";
properties["quartz.scheduler.instanceId"] = "instance_one";
properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
properties["quartz.threadPool.threadCount"] = "10";
properties["quartz.threadPool.threadPriority"] = "Normal";
properties["quartz.jobStore.misfireThreshold"] = "60000";
properties["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
properties["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz";
properties["quartz.jobStore.useProperties"] = "false";
properties["quartz.jobStore.dataSource"] = "default";
properties["quartz.jobStore.tablePrefix"] = "QRTZ_";
properties["quartz.jobStore.clustered"] = clustered.ToString();
properties["quartz.dataSource.default.connectionString"] = "Server=.;Database=Quartz;Trusted_Connection=True;";
if (extraProperties != null)
{
foreach (string key in extraProperties.Keys)
{
properties[key] = extraProperties[key];
}
}
if (connectionStringId == "SQLServer" || connectionStringId == "SQLite")
{
// if running MS SQL Server we need this
properties["quartz.jobStore.lockHandler.type"] =
"Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz";
}
//properties["quartz.dataSource.default.connectionString"] = (string)dbConnectionStrings[connectionStringId];
properties["quartz.dataSource.default.provider"] = dbProvider;
// First we must get a reference to a scheduler
ISchedulerFactory sf = new StdSchedulerFactory(properties);
sched = sf.GetScheduler();
String[] triggerGroups = sched.TriggerGroupNames;
sched.Start();
// Thread.Sleep(600*1000);
}
protected override void OnStop()
{
sched.Shutdown(true);
logger.Info("停止服务,时间: " + DateTime.Now.ToString());
}
问题2:触发器执行结束后被Quartz.net服务从数据库表删除。
通常我们使用mssql作业添加一个计划后都不希望计划结束后被无故删除。故本人对quartz.net进行改造。采用笨办法,将删除触发器的方法注释掉。 Quartz.Impl.AdoJobStore 下的类ConnectionAndTransactionHolder的RemoveTrigger 方法注释掉,让其永远返回true。
问题3:将Quartz.net的计划按Mssql作业计划的方式实现。
解决措施:认真分析crontab表达式,逐个将按天,按周,按月等不同调度周期的crontab分析透,再通过UI界面优化地展示给用户,以crontab或者simple方式存储。
问题4:如何立即启动任务。
立即启动任务实现其实相对简单,只需添加一个马上执行的触发器即可,但需要又不能将触发器在任务计划管理那显示出来,故可以采用隐藏立即触发触发器或者执行完后清理立即触发触发器的方式实现。
除此之外,还完善了任务日志,新增任务组,任务步骤管理功能,扩展任务属性,如创建时间,状态等等。由于笔者知识经验所限,方案未必完美,借博客园宝地抛砖引玉,还望各位大师不吝赐教。