Quartz调度源码分析【面试+工作】

前言

本文将具体分析Quartz是如何调度的,是如何通过数据库的方式来现在分布式调度。

调度线程

Quartz内部提供的调度类是QuartzScheduler,而QuartzScheduler会委托QuartzSchedulerThread去实时调度;当调度完需要去执行job的时候QuartzSchedulerThread并没有直接去执行job,

而是交给ThreadPool去执行job,具体使用什么ThreadPool,初始化多线线程,可以在配置文件中进行配置:

常用的线程池是SimpleThreadPool,这里默认启动了10个线程,在SimpleThreadPool会创建10个WorkerThread,由WorkerThread去执行具体的job;

调度分析

QuartzSchedulerThread是调度的核心类,具体Quartz是如何实现调度的,可以查看QuartzSchedulerThread核心源码:

1.halted和paused

这是两个boolean值的标志参数,分别表示:停止和暂停;halted默认为false,当QuartzScheduler执行shutdown()时才会更新为true;paused默认是true,当QuartzScheduler执行start()时

更新为false;正常启动之后QuartzSchedulerThread就可以往下执行了;

2.availThreadCount

查询SimpleThreadPool是否有可用的WorkerThread,如果availThreadCount>0可以往下继续执行其他逻辑,否则继续检查;

3.acquireNextTriggers

查询一段时间内将要被调度的triggers,这里有3个比较重要的参数分别是:idleWaitTime,maxBatchSize,batchTimeWindow,这3个参数都可以在配置文件中进行配置:

idleWaitTime:在调度程序处于空闲状态时,调度程序将在重新查询可用触发器之前等待的时间量(以毫秒为单位),默认是30秒;

batchTriggerAcquisitionMaxCount:允许调度程序节点一次获取(用于触发)的触发器的最大数量,默认是1;

batchTriggerAcquisitionFireAheadTimeWindow:允许触发器在其预定的火灾时间之前被获取和触发的时间(毫秒)的时间量,默认是0;

往下继续查看acquireNextTriggers方法源码:

可以发现只有在设置了acquireTriggersWithinLock或者batchTriggerAcquisitionMaxCount>1情况下才使用LOCK_TRIGGER_ACCESS锁,也就是说在默认参数配置的情况下,这里是没有使用锁的,

那么如果多个节点同时去执行acquireNextTriggers,会不会出现同一个trigger在多个节点都被执行?

注:acquireTriggersWithinLock可以在配置文件中进行配置:

acquireTriggersWithinLock:获取triggers的时候是否需要使用锁,默认是false,如果batchTriggerAcquisitionMaxCount>1最好同时设置acquireTriggersWithinLock为true;

带着问题继续查看TransactionCallback内部的acquireNextTrigger方法源码:

首先看一下在执行selectTriggerToAcquire方法时引入了新的参数:misfireTime=当前时间-MisfireThreshold,MisfireThreshold可以在配置文件中进行配置:

misfireThreshold:叫触发器超时,比如有10个线程,但是有11个任务,这样就有一个任务被延迟执行了,可以理解为调度引擎可以忍受这个超时的时间;具体的查询SQL如下所示:

这里的noLaterThan=当前时间+idleWaitTime+batchTriggerAcquisitionFireAheadTimeWindow,

noEarlierThan=当前时间-MisfireThreshold;

在查询完之后,会遍历执行updateTriggerStateFromOtherState()方法更新trigger的状态从STATE_WAITING到STATE_ACQUIRED,并且会判断rowsUpdated是否大于0,这样就算多个节点都查询到相同的trigger,但是肯定只会有一个节点更新成功;更新完状态之后,往qrtz_fired_triggers表中插入一条记录,表示当前trigger已经触发,状态为STATE_ACQUIRED;

4.executeInNonManagedTXLock

Quartz的分布式锁被用在很多地方,下面具体看一下Quartz是如何实现分布式锁的,executeInNonManagedTXLock方法源码如下:

大致分成3个步骤:获取锁,执行逻辑,释放锁;getLockHandler().obtainLock表示获取锁txCallback.execute(conn)表示执行逻辑,commitConnection(conn)表示释放锁

Quartz的分布式锁接口类是Semaphore,默认具体的实现是StdRowLockSemaphore,具体接口如下:

具体看一下obtainLock()是如何获取锁的,源码如下:

obtainLock首先判断是否已经获取到锁,如果没有执行方法executeSQL,其中有两条重要的SQL,分别是:expandedSQL和expandedInsertSQL,以SCHED_NAME = ‘myScheduler’为例:

select语句后面添加了FOR UPDATE,如果LOCK_NAME存在,当多个节点去执行此SQL时,只有第一个节点会成功,其他的节点都将进入等待;

如果LOCK_NAME不存在,多个节点同时执行expandedInsertSQL,只会有一个节点插入成功,执行插入失败的节点将进入重试,重新执行expandedSQL;

txCallback执行完之后,执行commitConnection操作,这样当前节点就释放了LOCK_NAME,其他节点可以竞争获取锁,最后执行了releaseLock;

5.triggersFired

表示触发trigger,具体代码如下:

首先查询trigger的状态是否STATE_ACQUIRED状态,如果不是直接返回null;然后通过通过jobKey获取对应的jobDetail,更新对应的FiredTrigger为EXECUTING状态;最后判定job的DisallowConcurrentExecution是否开启,如果开启了不能并发执行job,那么trigger的状态为STATE_BLOCKED状态,否则为STATE_WAITING;如果状态为STATE_BLOCKED,那么下次调度

对应的trigger不会被拉取,只有等对应的job执行完之后,更新状态为STATE_WAITING之后才可以执行,保证了job的串行;

6.执行job

通过ThreadPool来执行封装job的JobRunShell;

问题解释

在文章Spring整合Quartz分布式调度中 可在历史中查找,最后做了几次测试分布式调度,现在可以做出相应的解释

1.同一trigger同一时间只会在一个节点执行

上文中可以发现Quartz使用了分布式锁和状态来保证只有一个节点能执行;

2.任务没有执行完,可以重新开始

因为调度线程和任务执行线程是分开的,认为执行在Threadpool中执行,互相不影响;

3.通过DisallowConcurrentExecution注解保证任务的串行

在triggerFired中如果使用了DisallowConcurrentExecution,会引入STATE_BLOCKED状态,保证任务的串行;

总结

本文从源码的角度大致介绍了一下Quartz调度的流程,当然太细节的东西没有去深入;通过本文大致可以对多节点调度产生的现象做一个合理的解释。

原文发布于微信公众号 - Java帮帮(javahelp)

原文发表时间:2018-10-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏运维小白

10.8 ps命令

监控系统状态 ps 查看系统进程 用法:ps aux、ps -elf STAT部分说明 D 不能中断的进程 R run状态的进程 S sleep状态的进程 ...

2236
来自专栏铭毅天下

上线必备 | 高性能ES5.X部署配置清单

| 题记 | 网上当前(截止20170914)流传的各种配置文件普通存在以下问题: 1)版本低,不能和当前版本5.X匹配。 2)5.X的配置本身较1.X,2...

40111
来自专栏Java进阶架构师

「mysql优化专题」主从复制面试宝典!面试官都没你懂得多!(11)

主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库;主数据库一般是准实时的业务数据库。

1083
来自专栏黑泽君的专栏

day67_Redis学习笔记_03

  上边的配置说明当前该【从redis服务器】所对应的【主redis服务器】的IP是192.168.5.128,端口是6379。

1193
来自专栏有困难要上,没有困难创造困难也要上!

CentOS5.5使用yum时报错Error: Cannot find a valid baseurl for repo: addons

41410
来自专栏Hongten

hadoop2-HBase的安装和测试

https://www.cnblogs.com/hongten/p/hongten_hadoop_hbase.html

2021
来自专栏雪胖纸的玩蛇日常

python3+django2 开发易语言网络验证(下)

2753
来自专栏技术小讲堂

ASP.NET AJAX(11)__ScriptManagerUpdatePanel的支持成员功能控制成员脚本控件支持成员ScriptMode和ScriptPathLoadScriptsBeforeU

ScriptManager的作用,这个不言而喻,它是整个的ASP.NET AJAX的核心 UpdatePanel的支持成员 static void Regist...

3156
来自专栏小狼的世界

在Centos 5.2下编译安装LAMP

首先使用Virtualbox安装一台CentOS 5.2的虚拟机,网络连接采用 Host-only Adapter,这样主客机之间可以互相访问,但是客机不能够上...

1092
来自专栏大数据实战演练

Nginx安装配置及使用

-bash:warning:setlocale:LC_CTYPE:cannot change locale(en_US.UTF-8):Nosuch fileor...

4292

扫码关注云+社区

领取腾讯云代金券