因为JDBC-Based JobStore在进行job注册、trigger注册、任务调度及执行过程中须要操作数据库,而且会波及到多张表,比方trigger注册的时候会依据不同状况写入triggers、simple_triggers或cron_triggers表,在执行工作的时候会读取和更新triggers、job_details、simple_triggers、cron_triggers、fired_triggers等。这些操作都有事务性要求:要么全副胜利、要么全副失败,否则就会导致数据不统一,最终会影响到工作的正确调度和执行。
Quartz的事务管理
JDBC-Based JobStore有两个JobStore的最终实现类,一个是JobStoreTX,一个是JobStoreCMT,都继承自抽象类JobStoreSupport。
这两个实现类都是为JDBC-Based JobStore提供事务管理能力的,其中JobStoreTX是本人实现事务管理的,事务的开启、commit、rollback都由JobStoreTX管制。
JobStoreCMT是依赖于容器来治理事务的,他把事务管理的职责交给了运行环境,他本人自身不做事务管理。比方能够交给Spring来进行事务管理。
具体应用哪一个JobStore是通过Quartz的配置文件指定的:
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
配置为JobStoreCMT的状况下须要在配置文件中额定指定nonManagedTXDataSource:不受Quartz治理的事务的数据源。
两种事务管理机制都反对一个属性:dontSetAutoCommitFalse,字面含意是不容许设置数据库连贯的autoCommit为false,理论含意就是不容许开启事务。这个参数的默认值为false:容许开启事务,在这个默认设置下,Quartz在获取到数据库连贯后会设置其autoCommit为false,也就是相当于开启了事务,个别状况下这个参数也不须要批改。
然而如果是采纳JobStoreCMT、交给容器治理事务的话,应该是能够设置dontSetAutoCommitFalse为true从而彻底交给容器来启用、commit、rollback事务的,这部分内容有待验证!
Quartz的锁机制
启用事务管理之后是不是就能够十拿九稳的确保工作的正确执行呢?在多任务并发、或者在cluster的环境下,并发工作可能存在同时拜访同一条数据的可能,仅仅是事务管理还不足以确保工作的正确执行,还须要引入锁机制。
Quartz提供了一个叫Semaphore的接口来实现锁,Semaphore接口有obtainLock、releaseLock、requiresConnection 3个办法,别离用来获取锁、开释锁、以及判断以后锁对象在执行锁操作的时候是否须要数据库连贯的反对。
从Semaphore的类构造能够看到他有两个不同的实现类:
- SimpleSemaphore:基于内存的锁机制
- DBSemaphore:基于数据库的锁机制
具体采纳哪种类型的锁能够通过配置文件指定:
org.quartz.jobStore.lockHandler.class=SimpleSemaphore
两种锁机制的区别
基于内存的锁机制能够称之为“轻量级锁”,操作速度快、资源占用少、锁等待时间短,不须要底层数据库的反对。然而基于内存的锁机制不能实现跨利用的锁,在集群环境下基于内存的锁机制无奈实现目标。
比照而言,基于数据库的锁是“重量级锁”,通过给数据库表(qrtz_lock)的某一行或者整张表加锁从而实现以后线程对资源的锁定。基于数据库的锁能够反对集群环境。
加锁与不加锁操作
JobStoreSupport中提供了两种数据库操作方法:
- executeInLock:加锁操作数据库
- executeWithoutLock:不加锁操作数据库
这是因为加锁操作数据库的时候会造成想要获取同一信息的其余线程的锁期待,轻则影响性能,重则造成操作超时从而影响工作的失常调度执行。所以,Quartz就提供了这两种数据库操作,只给那些对数据十分敏感的操作加锁,非必要的状况下就不加锁。
比方在注册job和trigger的时候就加锁,因为注册操作并不是一个数据库操作、而是一系列数据库操作,只有所有的注册操作实现之后,能力容许调度工作开始调度该作业,所以注册操作必须加锁执行。
而某些查问性能比方getTriggerState、retrieveTrigger等等就不须要加锁,所以采纳不加锁形式拜访数据库,无疑会进步性能、无效防止锁超时、进步利用性能。
Quartz的锁对象
从Quartz须要拜访的资源来看,须要上锁的有两种:
- TRIGGER_ACCESS:也就是拜访TRIGGER的时候须要上锁,这也比拟容易了解,因为不论是作业的注册、还是调度执行,须要频繁操作TRIGGER
- STATE_ACCESS:拜访集群服务器状态表(qrtz_scheduler_state)的时候须要上锁,因为集群环境下多个服务器可能须要同时拜访、更新状态表
内存锁SimpleSemaphore
Quartz在非集群环境下的默认锁机制为内存锁SimpleSemaphore。
SimpleSemaphore提供一个锁容器(HashSet)locks,以及一个ThreadLocal变量lockOwners。
obtainLock办法:加锁操作前获取锁资源(TRIGGER_ACCESS或STATE_ACCESS)。首先查看lockOwners,如果锁资源曾经被以后线程获取(在lockOwners中)则无需期待、间接返回。否则,查看locks中是否存在该资源,存在的话阐明以后锁资源曾经被其余线程获取,则以后线程需期待锁开释。一旦其余线程开释后,则以后线程将该锁资源存入locks,同时注销lockOwners。
releaseLock办法:加锁操作执行实现后开释锁资源。查看lockOwners如果以后线程曾经锁定该资源的话,则将以后锁资源从lockOwners和locks中移除。
DBSemaphore基于数据库的锁
DBSemaphore是抽象类,有两个落地实现类StdRowLockSemaphore和UpdateLockRowSemaphore:
- StdRowLockSemaphore:通过select ... for update实现行锁,Quartz默认应用
- UpdateLockRowSemaphore:通过update 语句实现行锁,对于不反对通过select for update加锁的数据库,比方 MS SQLServer,须要采纳UpdateLockRowSemaphore
DBSemaphore的原理其实也非常简单:当Quartz判断某一操作须要锁定资源的时候,首先辨别一下须要行级锁还是表级锁,如果须要行级锁则通过select ... for update锁定qrtz_locks表中的指定行(TRIGGER_ACCESS或STATE_ACCESS),当须要表级锁的时候就应用insert语句锁定整张表。
obtainLock办法:其实就是执行上述行级锁或表级锁的操作,但因为数据库锁的开销比拟大,所以在执行锁定之前首先通过lockOwners判断以后线程是否曾经取得了锁,曾经取得锁的话就不再执行sql语句去取得锁了,节约开销。
releaseLock办法:从lockOwners移除锁资源,数据库锁是不须要显式的操作去开释的,事务提交或回滚之后天然就开释了锁。
小结
明天实现了Quratz的事务管理及锁机制的剖析,Quartz的集群治理稍后剖析。
Thanks a lot!
上一篇 Quartz - JDBC-Based JobStore