JDBC-BasedJobStore在作业注册、触发器注册、任务调度和执行过程中需要操作数据库,会涉及到多个表,比如触发器注册triggers、simple_triggers或者cron_triggers表会根据不同情况写入,triggers、job_details、simple_triggers、cron_triggers、fired_triggers等在任务执行时会被读取更新。这些操作有事务性要求:要么全部成功,要么全部失败,否则会导致数据不一致,最终影响任务的正确调度和执行。Quartz的事务管理JDBC-BasedJobStore有两个JobStore的最终实现类,一个是JobStoreTX,一个是JobStoreCMT,都继承自抽象类JobStoreSupport。这两个实现类为JDBC-BasedJobStore提供了事务管理能力,而JobStoreTX自己实现了事务管理,事务的开启、提交、回滚都由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,这样就可以完全交给容器去启用、提交、回滚事务了。这部分内容需要验证!Quartz的锁机制启用事务管理后,能否万无一失的保证任务的正确执行呢?在多任务并发或集群环境中,并发任务可能同时访问同一块数据。单靠事务管理不足以保证任务的正确执行,需要引入锁机制。Quartz提供了一个叫做Semaphore的接口来实现锁。Semaphore接口有obtainLock、releaseLock、requiresConnection三个方法,分别用于获取锁、释放锁、判断当前锁对象在进行锁操作时是否需要数据库连接支持。.从Semaphore的类结构可以看出它有两个不同的实现类:SimpleSemaphore:基于内存的锁机制DBSemaphore:基于数据库的锁机制,可以通过配置文件:org.quartz.jobStore指定锁的类型.lockHandler.class=SimpleSemaphore两种锁机制的区别基于内存的锁机制可以称为“轻量级锁”,运行速度快,占用资源少,锁等待时间短,不需要底层数据库的支持。但是基于内存的加锁机制无法实现跨应用加锁,基于内存的加锁机制在集群环境下也无法达到目的。相比之下,基于数据库的锁是“重量级锁”。通过锁定数据库表的一行或整张表(qrtz_lock),当前线程锁定资源。基于数据库的锁可以支持集群环境。加锁和解锁操作JobStoreSupport提供了两种数据库操作方法:executeInLock:锁住数据库executeWithoutLock:不锁数据库这是因为当使用锁操作数据库时,其他想要获取相同信息的线程会被阻塞锁等待至少会影响性能,并导致操作超时,影响任务的正常调度和执行。所以Quartz提供了这两种数据库操作,并且只对那些对数据非常敏感的操作加锁,没有必要的时候不加锁。比如注册job和trigger时加锁,因为注册操作不是数据库操作,而是一系列的数据库操作,只有注册操作全部完成后,才能让调度任务开始调度job,所以注册操作必须加锁执行。一些查询函数如getTriggerState、retrieveTrigger等是不需要加锁的,所以在不加锁的情况下访问数据库无疑会提高性能,有效避免加锁超时,提高应用性能。从Quartz需要访问的资源来看,需要加锁的锁对象有两种:TRIGGER_ACCESS:即访问TRIGGER时,需要加锁。这个也比较好理解,因为无论是作业注册还是调度执行,都需要频繁操作TRIGGERSTATE_ACCESS:访问集群服务器状态表(qrtz_scheduler_state)时,需要加锁,因为集群环境下多台服务器可能需要同时访问和更新状态表。.SimpleSemaphore提供了一个锁容器(HashSet)锁,以及一个ThreadLocal变量lockOwners。obtainLock方法:在进行锁操作前获取锁资源(TRIGGER_ACCESS或STATE_ACCESS)。首先查看lockOwners,如果锁资源已经被当前线程(在lockOwners中)获取到,则直接返回,不等待。否则,检查资源是否存在于锁中。如果存在,说明当前锁资源已经被其他线程获取,当前线程需要等待锁释放。一旦其他线程被释放,当前线程将锁资源存储到锁中,同时注册lockOwners。releaseLock方法:锁操作完成后释放锁资源。检查lockOwners如果当前线程已经锁定了资源,则从lockOwners和locks中移除当前的锁资源。DBSemaphore基于数据库的锁DBSemaphore是一个抽象类,有两个登陆实现类StdRowLockSemaphore和UpdateLockRowSemaphore:StdRowLockSemaphore:通过select...forupdate实现行锁,Quartz默认使用UpdateLockRowSemaphore:通过update语句实现行锁,对于那些不支持通过select锁定行锁更新锁定的数据库,如MSSQLServer,需要使用UpdateLockRowSemaphoreDBSemaphore。DBSemaphore的原理其实很简单:当Quartz判断某个操作需要对资源进行加锁时,先区分需要行级锁还是表级锁。如果需要行级锁则使用select...forupdate锁定qrtz_locks表中的指定行(TRIGGER_ACCESS或STATE_ACCESS),需要表级锁时使用insert语句锁定整个表。obtainLock方法:其实就是执行上面的行级锁或者表级锁操作,但是因为数据库锁的开销比较大,所以在执行锁之前先判断当前线程是否已经获取到锁通过lockOwners,如果已经获取到锁,就不再执行获取锁的sql语句,节省开销。releaseLock方法:从lockOwners中移除锁资源。数据库锁不需要通过显式操作来释放。事务提交或回滚后锁自然释放。总结今天Quartz的事务管理和锁机制分析完毕,后面再分析Quartz的集群管理。多谢!上一篇Quartz-基于JDBC的JobStore下一篇Quartz-集群配置及failOver原理
