共计 3211 个字符,预计需要花费 9 分钟才能阅读完成。
因为 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