java开源任务调度管理_通过源码分析Java开源任务调度框架Quartz的主要流程

通过源码分析Java开源任务调度框架Quartz的主要流程

从使用效果、调用链路跟踪、E-R图、循环调度逻辑几个方面分析Quartz。

系统说明:

IDE: IntelliJ

JDK:1.8

Quartz:2.2.1

使用效果

相信读者都有一定工作经验,这些细节不赘述。

2.本文采用Mysql数据库。

请执行 resources/scripts/tables_mysql_innodb.sql

3.修改jdbc.properties中数据库配置

4.通过IDEA, Edit Configurations -> Add Tomcat Server, 部署到Tomcat

e159f90e9aab3aaa597636156805572d.png

暴露的Restful 接口 /say-hello.do 以及添加好任务后的调用效果:

85884565ea9b333a57639612c58bc7f4.png

添加任务

在tomcat启动成功后,在首页点击“添加任务”,添加如下任务:

e9b3118366a887218e90e50b698d3962.png

代码执行逻辑在SyncJobFactory类中,从Output中可以看到执行的输出信息,

调用链跟踪的最后会回到这个类来。

00ad7107c21532ee3d23ec00672db495.png

现在开始跟踪调用链路。

IDEA 快捷键:进入方法:  Ctrl + 鼠标左键光标前进/后退: Ctrl + Shirt + 右方向键/左方向键

一、 调用链路跟踪

从配置文件applicationContext.xml配置中找到任务调度核心类SchedulerFactoryBean

resources/applicationContext.xml

...

使用IDEA快捷键,点击进入SchedulerFactoryBean类,它实现了InitializingBean接口,

在Spring中凡是实现了InitializingBean接口的Bean,都会在Bean属性都设置完成后调用afterPropertiesSet()方法.

SchedulerFactoryBean.java

//---------------------------------------------------------------------//Implementation of InitializingBean interface//实现 InitializingBean 接口//---------------------------------------------------------------------

public void afterPropertiesSet() throwsException {//...//Create SchedulerFactory instance.//创建 SchedulerFactory 调度器工厂实例

SchedulerFactory schedulerFactory =(SchedulerFactory)

BeanUtils.instantiateClass(this.schedulerFactoryClass);

initSchedulerFactory(schedulerFactory);//...//Get Scheduler instance from SchedulerFactory.//通过调度器工厂 获取 调度器实例

try{this.scheduler = createScheduler(schedulerFactory, this.schedulerName);//...

}

SchedulerFactoryBean.java

/*** Create the Scheduler instance for the given factory and scheduler name.

* 通过制定工厂和调度器名称创建调度器实例

* Called by {@link#afterPropertiesSet}.

*

The default implementation invokes SchedulerFactory's getScheduler

* method. Can be overridden for custom Scheduler creation.*/

protectedScheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName)throwsSchedulerException {//...

try{

SchedulerRepository repository=SchedulerRepository.getInstance();synchronized(repository) {

Scheduler existingScheduler= (schedulerName != null ? repository.lookup(schedulerName) : null);

Scheduler newScheduler=schedulerFactory.getScheduler();if (newScheduler ==existingScheduler) {throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " +

"in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!");

}//...

}

这个项目走的逻辑是 StdSchedulerFactory.getScheduler()方法,可自行debug。

StdSchedulerFactory.java

/*** Returns a handle to the Scheduler produced by this factory.

* 返回该工厂创造的调度器的句柄*/

public Scheduler getScheduler() throwsSchedulerException {if (cfg == null) {

initialize();

}

SchedulerRepository schedRep=SchedulerRepository.getInstance();

Scheduler sched=schedRep.lookup(getSchedulerName());//...

sched =instantiate();returnsched;

}

StdSchedulerFactory.java

private Scheduler instantiate() throwsSchedulerException {//...//大量的配置初始化、实例化代码//...//第1298行代码

qs = newQuartzScheduler(rsrcs, idleWaitTime, dbFailureRetry);//...

}

QuartzScheduler.java

/*** Create a QuartzScheduler with the given configuration

* 根据给定的配置 创建Quartz调度器*/

public QuartzScheduler(QuartzSchedulerResources resources, long idleWaitTime, @Deprecated longdbRetryInterval)throwsSchedulerException {this.resources =resources;if (resources.getJobStore() instanceofJobListener) {

addInternalJobListener((JobListener)resources.getJobStore());

}//private QuartzSchedulerThread schedThread;

this.schedThread = new QuartzSchedulerThread(this, resources);

ThreadExecutor schedThreadExecutor=resources.getThreadExecutor();//通过线程池执行 Quartz调度器线程

schedThreadExecutor.execute(this.schedThread);//...

}

QuartzSchedulerThread.java

/***

* The main processing loop of the QuartzSchedulerThread.

* Quartz调度器线程的主循环逻辑

*

*/@Overridepublic voidrun() {//while循环执行,只要调度器为被暂停

while(!halted.get()){

JobRunShell shell= null;try{

shell=qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);

shell.initialize(qs);

}if (qsRsrcs.getThreadPool().runInThread(shell) == false){}

}

}

JobRunShell.java

public voidrun() {//...

Job job =jec.getJobInstance();//...

try{

log.debug("Calling execute on job " +jobDetail.getKey());//执行

job.execute(jec);

endTime=System.currentTimeMillis();

}//...//更新Trigger触发器状态,删除FIRED_TRIGGERS触发记录

instCode =trigger.executionComplete(jec, jobExEx);//...

}

QuartzJobBean.java

/*** This implementation applies the passed-in job data map as bean property

* values, and delegates to executeInternal afterwards.

* 这个实现 把传入的map数据作为bean属性值,然后委托给 executeInternal 方法*/

public final void execute(JobExecutionContext context) throwsJobExecutionException {try{//执行

executeInternal(context);

}

SyncJobFactory.java

//回到了我们的业务类SyncJobFactory的executeInternal方法,//里面执行我们的业务代码

protected void executeInternal(JobExecutionContext context) throwsJobExecutionException {try{

LOG.info("SyncJobFactory execute" + IPAddressKowalski.getIpAddressAndPort() + " port:"+IPAddressKowalski.getTomcatPort());

}//...

System.out.println("jobName:" + scheduleJob.getJobName() + " " +scheduleJob);//...

}

二、E-R图

梳理6张主要的Quartz表:

8a4a4e135a40b2df0f3cb65e82310ee5.png

QRTZ_TRIGGERS 触发器表

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。 联合主键,QRTZ_JOB_DETAILS表SCHED_NAME外键

JOB_NAME,任务名。自定义值。 联合主键,QRTZ_JOB_DETAILS表JOB_NAME外键

JOB_GROUP,任务组。 自定义值。联合主键,QRTZ_JOB_DETAILS表JOB_GROUP外键

TRIGGER_STATE,触发器状态: WAITING , ACQUIRED, BLOCKING

NEXT_FIRE_TIME, 下次触发时间:

MISFIRE_INSTR,执行失败后的指令,

非失败策略 MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;

失败策略 MISFIRE_INSTRUCTION_SMART_POLICY = 0;

TRIGGER_TYPE, 触发器类型,例如CRON,cron表达式类型的触发器

PRIORITY,优先级

QRTZ_CRON_TRIGGERS cron类型触发器表

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。 联合主键,QRTZ_TRIGGERS表SCHED_NAME外键

JOB_NAME,任务名。自定义值。 联合主键,QRTZ_TRIGGERS表JOB_NAME外键

JOB_GROUP,任务组。 自定义值。联合主键,QRTZ_TRIGGERS表JOB_GROUP外键

CRON_EXPRESSION, cron表达式, 例如每30秒执行一次, 0/30 * * * * ?

QRTZ_JOB_DETAILS 任务详细表

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。联合主键

JOB_NAME,任务名。自定义值。 联合主键

JOB_GROUP,任务组。 自定义值。联合主键

JOB_DATA,blob类型,任务参数

QRTZ_FIRED_TRIGGERS 任务触发表

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。联合主键

ENTRY_ID,entry id,联合主键

JOB_NAME,任务名。自定义值。

JOB_GROUP,任务组。 自定义值。

FIRED_TIME, 任务触发时间

STATE,状态

INSTANCE_NAME, 服务器实例名

PRIORITY,优先级

QRTZ_SCHEDULER_STATE

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。联合主键

INSTANCE_NAME,服务器实例名。联合主键

LAST_CHECKIN_TIME,上次检查时间

CHECKIN_INTERVAL,检查间隔

QRTZ_LOCKS 全局锁

SCHED_NAME,调度器名称,集群时为常量值:“ClusterScheduler”。联合主键

LOCK_NAME,锁名称,例如,TRIGGER_ACCESS。联合主键

三、循环调度逻辑

主要流程如下:

3192bb603758cb0baa675606b88ed9c5.png

源码如下:

QuartzSchedulerThread.java

public voidrun() {//...

while (!halted.get()) {try{//合理休眠//...//获取接下来的触发器//1.状态为WAITING//2.触发时间在30秒内//3.不是错过执行的或者错过了但是时间不超过两分钟

triggers =qsRsrcs.getJobStore().acquireNextTriggers(

now+idleWaitTime, Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());//...//触发任务

List res =qsRsrcs.getJobStore().triggersFired(triggers);//...

JobRunShell shell = null;//...//执行代码

if (qsRsrcs.getThreadPool().runInThread(shell) == false) {//...

} //while (!halted)//..

}

JobRunShell.java

protected QuartzScheduler qs = null;public voidrun() {

qs.addInternalSchedulerListener(this);try{//...

do{

Job job=jec.getJobInstance();//execute the job

try{//执行任务代码

job.execute(jec);//更新触发器,删除触发记录

qs.notifyJobStoreJobComplete(trigger, jobDetail, instCode);break;

}while (true);

}//...

}

四、扩展

除了对主线程 QuartzSchedulerThread 的分析

继续分析JobStoreSupport类的两个线程 ClusterManager 和 MisfireHandler 的分析, 它们维护触发器的MISFIRE_INSTR状态,和调度器状态QRTZ_SCHEDULER_STATE。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值