Quartz.NET开源作业调度框架系列(五):AdoJobStore保存job到数据库

简介: 在Quartz.NET中主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。某些任务需要对数据库中的数据进行增删改处理 , 这些任务不能并发执行,就需要用到无状态的任务 , 否则会造成数据混乱。

  Quartz.NET 任务调度的核心元素是 scheduler, trigger 和 job,其中 trigger(用于定义调度时间的元素,即按照什么时间规则去执行任务) 和 job 是任务调度的元数据,scheduler 是实际执行调度的控制器。在Quartz.NET中主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。某些任务需要对数据库中的数据进行增删改处理 , 这些任务不能并发执行,就需要用到无状态的任务 , 否则会造成数据混乱。

  另外有些情况下,我们需要将任务保存到数据库中,特别是有些任务中包含参数,例如累加的任务,如果可以保存到数据库中,即便中间断电或者程序异常重启,中间计算的结果也不会丢失,可以从断点的结果进行运算(首先恢复任务),下面介绍一下如何用AdoJobStore将任务保存到SQL Server数据库中.

  事先要在数据库上新建一个QRTZ_数据库,并执行SQL建表脚本:

1.jpg

1 RecoveryJob


  是一个无状态的任务,代码如下:

usingSystem;
usingSystem.Collections.Specialized;
usingSystem.Threading;
usingCommon.Logging;
usingQuartz;
usingQuartz.Impl;
usingQuartz.Job;
usingSystem.Windows.Forms;
namespaceQuartzDemo{
/// <summary>/// 无状态的可恢复的任务/// </summary>publicclassRecoveryJob : IJob    {
privateconststringCount="count";
publicvirtualvoidExecute(IJobExecutionContextcontext)
        {
JobKeyjobKey=context.JobDetail.Key;
if (isOpen("FrmConsole"))
            {
try                {
//获取当前Form1实例__instance= (FrmConsole)Application.OpenForms["FrmConsole"];
// 如果任务是恢复的任务的话if (context.Recovering)
                    {
__instance.SetInfo(string.Format("{0} RECOVERING at {1}", jobKey, DateTime.Now.ToString("r")));
                    }
else                    {
__instance.SetInfo(string.Format("{0} starting at {1}", jobKey, DateTime.Now.ToString("r")));
                    }
JobDataMapdata=context.JobDetail.JobDataMap;
intcount;
if (data.ContainsKey(Count))
                    {
//是否能从数据库中恢复,如果保存Job等信息的话,程序运行突然终端(可用调试时中断运行,而不是关闭窗体来模拟)count=data.GetInt(Count);
                    }
else                    {
count=0;
                    }
count++;
data.Put(Count, count);
__instance.SetInfo(string.Format(" {0} Count #{1}", jobKey, count));
                }
catch (Exceptionex)
                {
Console.WriteLine(ex.Message);
                }
            }
        }
privatestaticFrmConsole__instance=null;
/// <summary>/// 判断窗体是否打开/// </summary>/// <param name="appName"></param>/// <returns></returns>privateboolisOpen(stringappName)
        {
FormCollectioncollection=Application.OpenForms;
foreach (Formformincollection)
            {
if (form.Name==appName)
                {
returntrue;
                }
            }
returnfalse;
        }
    }
}

2 RecoveryStatefulJob


  是一个有状态的任务,和无状态的区别就是在任务类的上面用[PersistJobDataAfterExecution]标注任务是有状态的 , 有状态的任务不允许并发执行,也需要标注 [DisallowConcurrentExecution],代码如下:

usingSystem;
usingSystem.Collections.Specialized;
usingSystem.Threading;
usingCommon.Logging;
usingQuartz;
usingQuartz.Impl;
usingQuartz.Job;
usingSystem.Windows.Forms;
namespaceQuartzDemo{
/// <summary>///  用这个[PersistJobDataAfterExecution]标注任务是有状态的,///  有状态的任务不允许并发执行 [DisallowConcurrentExecution]/// </summary>    [PersistJobDataAfterExecution]
    [DisallowConcurrentExecution]
publicclassRecoveryStatefulJob : RecoveryJob    {
    }
}

3 AdoJobStoreExample


 用 properties["quartz.dataSource.default.connectionString"] = "Server=(local);Database=QRTZ_;Trusted_Connection=True;";定义了数据库的连接信息,程序运行时会自动将任务保存到数据库中:

usingSystem;
usingSystem.Collections.Specialized;
usingSystem.Threading;
usingCommon.Logging;
usingQuartz;
usingQuartz.Impl;
usingQuartz.Job;
usingSystem.Windows.Forms;
namespaceQuartzDemo{
/// <summary>///  AdoJobStore的用法示例/// </summary>publicclassAdoJobStoreExample    {
publicvirtualvoidRun(boolinClearJobs, boolinScheduleJobs)
        {
NameValueCollectionproperties=newNameValueCollection();
properties["quartz.scheduler.instanceName"] ="TestScheduler";
properties["quartz.scheduler.instanceId"] ="instance_one";
properties["quartz.threadPool.type"] ="Quartz.Simpl.SimpleThreadPool, Quartz";
properties["quartz.threadPool.threadCount"] ="5";
properties["quartz.threadPool.threadPriority"] ="Normal";
properties["quartz.jobStore.misfireThreshold"] ="60000";
properties["quartz.jobStore.type"] ="Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
properties["quartz.jobStore.useProperties"] ="false";
properties["quartz.jobStore.dataSource"] ="default";
properties["quartz.jobStore.tablePrefix"] ="QRTZ_";
properties["quartz.jobStore.clustered"] ="true";
// SQLite// properties["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz";properties["quartz.jobStore.driverDelegateType"] ="Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz";
    // 数据库连接字符串properties["quartz.dataSource.default.connectionString"] ="Server=(local);Database=QRTZ_;Trusted_Connection=True;";
properties["quartz.dataSource.default.provider"] ="SqlServer-20";
// First we must get a reference to a schedulerISchedulerFactorysf=newStdSchedulerFactory(properties);
ISchedulersched=sf.GetScheduler();
boolb是否恢复=false;
if (inClearJobs)
            {
Console.WriteLine("***** Deleting existing jobs/triggers *****");
// sched.Clear();            }
if (inScheduleJobs)
            {
stringschedId=sched.SchedulerInstanceId;
intcount=1;
//定义一个无状态的任务IJobDetailjob=JobBuilder.Create<RecoveryJob>()
                    .WithIdentity("recoveryjob_"+count, schedId)
                    .RequestRecovery() //recovery                    .Build();
ISimpleTriggertrigger= (ISimpleTrigger)TriggerBuilder.Create()
                                                              .WithIdentity("triger_"+count, schedId)
                                                              .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
                                                              .WithSimpleSchedule(x=>x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3)))
                                                              .Build();
//可用此来查看定义的触发器触发规则//log.InfoFormat("{0} will run at: {1} and repeat: {2} times, every {3} seconds",//job.Key, trigger.GetNextFireTimeUtc(),//trigger.RepeatCount,//trigger.RepeatInterval.TotalSeconds);try                {
//如果数据库已经存在同名job和trigger,则绑定失败sched.ScheduleJob(job, trigger);
                }
catch                {
b是否恢复=true;
                }
count++;
//定义一个有状态的任务***********************************************************job=JobBuilder.Create<RecoveryStatefulJob>()
                    .WithIdentity("Statefuljob_"+count, schedId)
                    .RequestRecovery() // recovery                    .Build();
trigger= (ISimpleTrigger)TriggerBuilder.Create()
                                               .WithIdentity("triger_"+count, schedId)
                                               .StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
                                               .WithSimpleSchedule(x=>x.WithRepeatCount(20).WithInterval(TimeSpan.FromSeconds(3)))
                                               .Build();
try                {
sched.ScheduleJob(job, trigger);
                }
catch                {
b是否恢复=true;
                }
            }
//启动sched.Start();
//sched.Shutdown();        }
publicstringName        {
get { returnGetType().Name; }
        }
publicvoidRun()
        {
boolclearJobs=true;
//clearJobs = false;boolscheduleJobs=true;
AdoJobStoreExampleexample=newAdoJobStoreExample();
example.Run(clearJobs, scheduleJobs);
        }
    }
}

4 效果


执行演示程序,具体的界面如下所示:

2.jpg

其中可以发现有状态的任务和无状态的区别,有状态的任务可以在不同的任务中共享变量,而无状态的则是隔离的,不能共享变量。可以看到有状态的计数每次累加1,而无状态的每次执行时都会丢失累加数(新的实例),中断程序,查看数据库的QRTZ_JOB_DETAILS表,可以看见还有一个持久化的任务:

1.jpg

中断程序后(调试状态时不关闭窗体,而是中断调试,模拟异常关闭) ,再重新运行可以看到如下界面:

2.jpg


相关文章
|
3天前
|
SQL Linux 数据库
|
4天前
|
关系型数据库 分布式数据库 数据库
PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。
【7月更文挑战第3天】PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。通过数据分片和水平扩展支持微服务弹性,保证高可用性,且兼容MySQL协议,简化集成。示例展示了如何使用Spring Boot配置PolarDB,实现服务动态扩展。PolarDB缓解了微服务数据库挑战,加速了开发部署,为云原生应用奠定基础。
20 3
|
4天前
|
关系型数据库 分布式数据库 PolarDB
**PolarDB开源指南:构建分布式数据库集群**踏上PolarDB开源之旅,了解如何从零开始搭建分布式集群
【7月更文挑战第3天】**PolarDB开源指南:构建分布式数据库集群**踏上PolarDB开源之旅,了解如何从零开始搭建分布式集群。采用存储计算分离架构,适用于大规模OLTP和OLAP。先准备硬件和软件环境,包括Linux、Docker和Git。然后,克隆源码,构建Docker镜像,部署控制节点和计算节点。使用PDCli验证集群状态,开始探索PolarDB的高性能与高可用性。在实践中深化学习,贡献于数据库技术创新。记得在安全环境下测试。
10 1
|
1天前
|
C# 开发者 Windows
4款.NET开源、功能强大的Windows桌面工具箱
4款.NET开源、功能强大的Windows桌面工具箱
|
1天前
|
C# C++
一款.NET开源、功能强大、跨平台的绘图库 - OxyPlot
一款.NET开源、功能强大、跨平台的绘图库 - OxyPlot
|
4天前
|
运维 Cloud Native 安全
荣誉加身!陶建辉被授予 GDOS 全球数据库及开源峰会荣誉顾问
**第二十三届 GOPS 全球运维大会暨 XOps 峰会在京召开,聚焦开源数据库与技术创新。涛思数据CEO陶建辉获GDOS全球数据库及开源峰会荣誉顾问称号,因其在TDengine数据库的开源与研发上的贡献。TDengine,高性能时序数据库,已在多个行业广泛应用,推动数据库技术发展。陶建辉将持续为开源生态和行业创新贡献力量。
16 0
|
16天前
|
编译器 C# Windows
一个基于Net Core3.0的WPF框架Hello World实例
一个基于Net Core3.0的WPF框架Hello World实例
|
16天前
|
关系型数据库 MySQL 数据库
MySQL数据库作业设计之豆瓣音乐
MySQL数据库作业设计之豆瓣音乐
17 0
|
18天前
|
关系型数据库 MySQL 项目管理
数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面
数据库大作业——基于qt开发的图书管理系统(四)项目目录的整理与绘制登录页面
|
18天前
|
SQL 关系型数据库 MySQL
数据库大作业——基于qt开发的图书管理系统(三)Qt连接Mysql数据库
数据库大作业——基于qt开发的图书管理系统(三)Qt连接Mysql数据库