Quartz.net持久化与集群部署开发详解
序言
我前边有几篇文章有介绍过quartz的基本使用语法与类库。但是他的执行计划都是被写在本地的xml文件中。无法做集群部署,我让它看起来脆弱不堪,那是我的罪过。
但是quart.net是经过许多大项目的锤炼,走到啦今天,支持集群高可用的开发方案那是一定的,今天我就给小结下我的quartz.net开发升级过程。
Quartz.net的数据库表结构
如果支持集群与持久化,单靠本机的内存和xml来保存计算任务调度的各种状态值,可想而知,是困难的。所以支持数据库这样的解决方案,OpenSymphony组织怎么可能会给遗漏。
官方提供的各种数据库脚本:https://github.com/quartznet/quartznet/tree/master/database/tables
下面我给大家展示下quartz任务调度的MS SQLSERVER表结构
创建表结构T-SQL脚本
部分我们扩展开发常用到的表及字段说明
1、QRTZ_JOB_DETAILS:存储的是job的详细信息,包括:[DESCRIPTION]描述,[IS_DURABLE]是否持久化,[JOB_DATA]持久化对象等基本信息。
2、QRTZ_TRIGGERS:触发器信息,包含:job的名,组外键,[DESCRIPTION]触发器的描述等基本信息,还有[START_TIME]开始执行时间,[END_TIME]结束执行时间,[PREV_FIRE_TIME]上次执行时间,[NEXT_FIRE_TIME]下次执行时间,[TRIGGER_TYPE]触发器类型:simple和cron,[TRIGGER_STATE]执行状态:WAITING,PAUSED,ACQUIRED分别为:等待,暂停,运行中。
3、QRTZ_CRON_TRIGGERS:保存cron表达式。
4、QRTZ_SCHEDULER_STATE:存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态,INSTANCE_NAME:之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字。 [LAST_CHECKIN_TIME]上次检查时间,[CHECKIN_INTERVAL]检查间隔时间。
5、QRTZ_PAUSED_TRIGGER_GRPS:暂停的任务组信息。
6、QRTZ_LOCKS,悲观锁发生的记录信息。
7、QRTZ_FIRED_TRIGGERS,正在运行的触发器信息。
8、QRTZ_SIMPLE_TRIGGERS,简单的出发器详细信息。
9、QRTZ_BLOB_TRIGGERS,触发器存为二进制大对象类型(用于Quartz用户自己触发数据库定制自己的触发器,然而JobStore不明白怎么存放实例的时候)。
.net程序配置quartz数据库参数
//1.首先创建一个作业调度池 var properties = new NameValueCollection(); //存储类型 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"] = Config.QuartzConnStr; //sqlserver版本 properties["quartz.dataSource.myDS.provider"] = "SqlServer-20"; //最大链接数 //properties["quartz.dataSource.myDS.maxConnections"] = "5"; // First we must get a reference to a scheduler ISchedulerFactory sf = new StdSchedulerFactory(properties); IScheduler sched = sf.GetScheduler();
上面便是创建一个调度器,以及配置与quartz数据库的详细参数。有啦数据库后你会很兴奋,直接写t-sql操作数据库多么便捷啊,并且所有的任务调度信息都一目了然,这里我要告诉你的事,你不需要直接写sql语句,quart提供的类库就能自动对数据库进行填充,以完成对任务调度的管理操作。
quartz.net任务调度的各种操作方法
首先我要说下,我前面有一篇文章对quartz.net做个基本的操作描述,这里也只贴代码,仅供参考。
1、添加任务计划,并制定要触发的执行类
#region 检查是否存在 if (IsExistJob(m.JobGroupName, m.JobName)) { return false; } #endregion #region 添加任务计划 if (m.StarTime == null) { m.StarTime = DateTime.Now; } DateTimeOffset starRunTime = DateBuilder.NextGivenSecondDate(m.StarTime, 1); if (m.EndTime == null) { m.EndTime = DateTime.MaxValue.AddDays(-1); } DateTimeOffset endRunTime = DateBuilder.NextGivenSecondDate(m.EndTime, 1); scheduler = GetScheduler(); IJobDetail job = JobBuilder.Create<QuartzFunction>() .WithIdentity(m.JobName, m.JobGroupName) .WithDescription(m.JobDescribe) .Build(); ICronTrigger trigger = (ICronTrigger)TriggerBuilder.Create() .StartAt(starRunTime) .EndAt(endRunTime) .WithIdentity(m.JobName, m.JobGroupName) .WithCronSchedule(m.CronStr) .WithDescription(m.JobDescribe) .Build(); scheduler.ScheduleJob(job, trigger); scheduler.Start(); #endregion #region 关联运行接口 var api = new A_RunJobTriggerEntity(); api.ApiCode = m.ApiCode; api.ApiType = m.ApiType; api.ApiUrl = m.ApiUrl; api.AppID = m.AppID; api.CreateTime = DateTime.Now; api.JobDescribe = m.JobDescribe; api.ServiceCode = m.ServiceCode; api.Token = m.Token; api.TriggerGroup = m.JobGroupName; api.TriggerName = m.JobName; new ARunJobRelationManage().Insert(api); #endregion
2、移除执行计划
scheduler = GetScheduler(); var trigger = new TriggerKey(jobGroup, jobName); scheduler.PauseTrigger(trigger);//停止触发器 scheduler.UnscheduleJob(trigger); //移除触发器 var result = scheduler.DeleteJob(JobKey.Create(jobName,jobGroup));
3、暂停指定任务计划
scheduler = GetScheduler(); scheduler.PauseJob(JobKey.Create(jobName, jobGroup));
4、暂停所有的任务计划
scheduler = GetScheduler();
scheduler.PauseAll();
5、开启指定的任务计划
scheduler = GetScheduler(); if (!scheduler.IsStarted) { scheduler.Start(); } //scheduler.ResumeTrigger(new TriggerKey(jobName, jobGroup)); scheduler.ResumeJob(JobKey.Create(jobName, jobGroup));
6、开启所有的任务计划
scheduler = GetScheduler(); if (!scheduler.IsStarted) { scheduler.Start(); } //scheduler.ResumeTrigger(new TriggerKey(jobName, jobGroup)); scheduler.ResumeAll();
7、调度启动状态,如果调度的状态没有开启,即便触发器的状态为等待执行,执行中,也是无效的。
scheduler = GetScheduler(); if (scheduler.IsStarted) { return "开启"; } else { return "关闭"; }
8、是否集群
//是否集群 //properties["quartz.jobStore.clustered"] = "false"; //properties["quartz.scheduler.instanceId"] = "AUTO";
检验下结果:
我在两台服务器上部署,设置一个任务计划,每2秒出发一次,2台机器同时跑,没有重复执行,且一台服务器down掉仍可准确无误的运行,给图。
总结
如果你在用quartz.net中有什么疑惑,或者有什么好的项目分享,欢迎加入右上角的群,我们一起学习进步。