基于Quartz.Net的任务管理平台开发(1) —— 基本原理
由于工作原因,需要用到任务监控平台,经过一番摸索,发现了Quratz.net这个开源的作业调度框架。防止忘记,做个备忘。
1 Quartz.Net概述
开源组件Quartz.Net,这是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中。它提供了巨大的灵活性而不牺牲简单性。能够用它来为执行一个作业而创建简单的或复杂的调度。
Quartz于很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。
Quartz.net是一个开源的任务调度工具,相当于数据库中的 Job、Windows 的计划任务、Unix/Linux 下的 Cron,但 Quartz 可以把排程控制的更精细,对任务调度的领域问题进行了高度的抽象,实现作业的灵活调度。
2 Quartz基本概念
Quartz由几个基本的接口组成,如下图:
Scheduler:
计划调度器,里面可以盛放众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行。
Trigger:
触发器,配置调度参数的配置,什么时候去调。描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
Job:
任务是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
JobDetail:
可执行的工作,它本身可能是有状态的。Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
JobDataMap:
存放Job运行时的信息。
Calendar:
日历功能,一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。
ThreadPool:
Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
SchedulerContext:
调度器的上下文
3 Quartz运行原理
3.1 任务调度原理
启动过程:
(1) 实例化一个调度器工厂SchedulerFactory,可以使用配置文件,或者代码实现自定义的实例。
(2) 通过getScheduler()方法从调度器工厂里得到调度器实例
(3) Scheduler有一个QuartzSchedulerThread(Thread的子类)属性,在scheduler实例化的时候,实例化了一个对象,并用ThreadExecutor启动该线程对象。该线程就是调度线程,主要任务就是不停的从JobStore中获取即将被触发的触发器(默认30s调度一次)。在这个时候调度线程虽然启动,但是处于pause状态。
调度过程:
(4) 通过scheduleJob()方法将任务和触发器存储在JobStore中,通过start()方法将QuartzSchedulerThread的pause状态设为false,通知调度线程执行任务,此后调度线程不停的从JobStore中去取即将触发的任务。
3.2 任务执行原理
(1) 调度线程首先去线程池中获取可用的线程,如果没有的话,就阻塞。
(2) 从JobStore(从存储介质中获取触发器,存储介质可以是内存也可以是数据库)获取(接下来30s内的)触发器,然后等待该触发器触发。
(3) 调度线程创建一个JobRunShell(就是一个Runnable),然后从线程池中调用线程执行该任务。
获取trigger、JobDetail以及生成Job实例,然后执行job的execute接口函数。
3.3 任务持久化
Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,第二种类型叫做JDBC作业存储。在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。也不方便于集群处理,因此本模块采用JDBC作业存储的方案,Quartz.net支持多种数据库进行任务持久化的存储。
官方提供的各种数据库脚本:https://github.com/quartznet/quartznet/tree/master/database/tables
3.4 Quartz表结构
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不明白怎么存放实例的时候)。
以上为Quartz.Net的基本原理。