1、基本信息:

     Quartz是一个开源的作业调度框架,它完全由java写成,并设计用于J2Se和J2EE应用中。它提供了巨大的灵活性而不牺牲简单性。你能够用它 来为执行一个作业而创建简单的或复杂的调度。它有很多特征,如:数据库支持,集群,插件,EJB作业预构建,JavaMail及其它,支持cron- like表达式等等。其中集群配置一般比较复杂


2 Quartz的集群配置:

     2.1 实现集群的基本原理

 

     Quartz是通过借助关系数据库和JDBC作业存储来实现集群管理的。


Java 主从 实现 集群 java quartz 集群_数据库

1、原理:     

          集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC-JobStore(JobStore TX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的(Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程)。

负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。

故 障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标 识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的"requests recovery"属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。

梳理一下其中的流程,可以表示为:

0.调度器线程(QuartzSchedulerThread)run()

1.获取待触发trigger

    1.1数据库LOCKS表TRIGGER_ACCESS行加锁

    1.2读取JobDetail信息

    1.3读取trigger表中触发器信息并标记为"已获取"

    1.4commit事务,释放锁

2.触发trigger

    2.1数据库LOCKS表STATE_ACCESS行加锁

    2.2确认trigger的状态

    2.3读取trigger的JobDetail信息

    2.4读取trigger的Calendar信息

    2.3更新trigger信息

    2.3commit事务,释放锁

3实例化并执行Job

    3.1从线程池获取线程执行JobRunShell的run方法

可以看到,这个过程中有两个相似的过程:同样是对数据表的更新操作,同样是在执行操作前获取锁 操作完成后释放锁.这一规则可以看做是quartz解决集群问题的核心思想.

          --任务详细信息表

                         

   

CREATE TABLE qrtz_job_details

    (

    JOB_NAME VARCHAR2(80) NOT NULL,

    JOB_GROUP VARCHAR2(80) NOT NULL,

    DESCRIPTION VARCHAR2(120) NULL,

    JOB_CLASS_NAME VARCHAR2(128) NOT NULL,

    IS_DURABLE VARCHAR2(1) NOT NULL,

    IS_VOLATILE VARCHAR2(1) NOT NULL,

    IS_STATEFUL VARCHAR2(1) NOT NULL,

    REQUESTS_RECOVERY VARCHAR2(1) NOT NULL, --可恢复标记

    JOB_DATA BLOB NULL,

    PRIMARY KEY (JOB_NAME,JOB_GROUP)

    );

          --触发器与任务关联表

                         

CREATE TABLE qrtz_fired_triggers

    (

    ENTRY_ID VARCHAR2(95) NOT NULL,

    TRIGGER_NAME VARCHAR2(80) NOT NULL,

    TRIGGER_GROUP VARCHAR2(80) NOT NULL,

    IS_VOLATILE VARCHAR2(1) NOT NULL,

    INSTANCE_NAME VARCHAR2(80) NOT NULL,

    FIRED_TIME NUMBER(13) NOT NULL,

    STATE VARCHAR2(16) NOT NULL,

    JOB_NAME VARCHAR2(80) NULL,

    JOB_GROUP VARCHAR2(80) NULL,

    IS_STATEFUL VARCHAR2(1) NULL,

    REQUESTS_RECOVERY VARCHAR2(1) NULL, --可恢复标记

    PRIMARY KEY (ENTRY_ID)

    );

                  --调度器状态表

                         

CREATE TABLE qrtz_scheduler_state

    (

    INSTANCE_NAME VARCHAR2(80) NOT NULL, --调度器实例ID

    LAST_CHECKIN_TIME NUMBER(13) NOT NULL, --上次检查时间

    CHECKIN_INTERVAL NUMBER(13) NOT NULL, --检查时间间隔

    RECOVERER VARCHAR2(80) NULL, --恢复调度器

    PRIMARY KEY (INSTANCE_NAME)

    );

3.1.2.1  Scheduler主要属性的配置

                        

  

# Scheduler主要属性的一般定义模式如下:

    #

    # org.quartz.scheduler.instanceName = SCHED_NAME

    # org.quartz.scheduler.instanceId = INSTANCE_ID

    # org.quartz.scheduler.threadName = THREAD_NAME

    # org.quartz.scheduler.rmi.export = false

    # org.quartz.scheduler.rmi.proxy = false

    # org.quartz.scheduler.rmi.registryHost = localhost

    # org.quartz.scheduler.rmi.registryPort = 1099

    # org.quartz.scheduler.rmi.createRegistry = never

    # org.quartz.scheduler.userTransactionURL = USER_TX_LOCATION

    # org.quartz.scheduler.wrapJobExecutionInUserTransaction = JOBS_IN_USER_TX

    # org.quartz.scheduler.idleWaitTime = IDLE_WAIT_TIME

    # org.quartz.scheduler.dbFailureRetryInterval = DB_FAILURE_RETRY_INTERVAL

    # org.quartz.scheduler.classLoadHelper.class = CLASS_LOAD_HELPER_CLASS

    # org.quartz.context.key.SOME_KEY = SOME_VALUE

Java 主从 实现 集群 java quartz 集群_Java 主从 实现 集群_02



3.1.2.2   线程池(ThreadPool)的配置

                        

    # 定制一个线程池的一般模式如下:


#

    # org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

    # org.quartz.threadPool.threadCount = THREAD_COUNT

    # org.quartz.threadPool.threadPriority = THREAD_PRIO

    #

    # 简单线程池(SimpleThreadPool)的选项参数:

    #

    # org.quartz.threadPool.makeThreadsDaemons = DAEMON_THREADS

    # org.quartz.threadPool.threadsInheritGroupOfInitializingThread = INHERIT_GRP

    # org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = INHERIT_LDR

    #

    # or

    #

    # org.quartz.threadPool.class = com.mycompany.goo.FooThreadPool

    # org.quartz.threadPool.somePropOfFooThreadPool = someValue

    #

Java 主从 实现 集群 java quartz 集群_Source_03

3.1.2.3 任务存储(JobStore)的配置

                        

# 定义一个任务存储的一般模式如下:

    #

    # org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

    # org.quartz.jobStore.misfireThreshold = MISFIRE_THRESHOLD

    #

    # or

    #

    # org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.<JobStoreClass>

    # JobStoreClass 是下面其中的一个:

    # - JobStoreTX 用于单机(standalone-Quartz)实现

    # - JobStoreCMT 用于基于应用服务器容器管理事务(appserver-based container-managed transaction )的Quartz 实现

    #

    # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.<DriverDelegateClass>

    # DriverDelegateClass 是下面其中的一个:

    # - StdJDBCDelegate (用于许多 JDBC-compliant drivers)

    # - MSSQLDelegate (用于 Microsoft SQL Server drivers)

    # - PostgreSQLDelegate (用于 PostgreSQL drivers)

    # - WebLogicDelegate (用于 WebLogic drivers)

    # - oracle.OracleDelegate (用于 Oracle drivers)

    #

    # org.quartz.jobStore.useProperties = USE_PROPERTIES

    # org.quartz.jobStore.dataSource = DS_NAME

    # org.quartz.jobStore.tablePrefix = TABLE_PREFIX

    # org.quartz.jobStore.isClustered = IS_CLUSTERED

    # org.quartz.jobStore.selectWithLockSQL = LOCKING_SELECT_STATEMENT

    # org.quartz.jobStore.dontSetAutoCommitFalse = DONT_TURN_OFF_AUTO_COMMIT

    # org.quartz.jobStore.maxMisfiresToHandleAtATime = MAX_MISFIRE_HANDLE

    # org.quartz.jobStore.txIsolationLevelSerializable = SERIALIZABLE_ISOLATION

    #

    # 如果你使用JobStoreCMT,你还需要下面的参数:

    #

    # org.quartz.jobStore.nonManagedTXDataSource = NON_MANAGED_TX_DS_NAME

    #

    # 并且如果你使用JobStoreCMT,下面的参数是可选的:

    #

    # org.quartz.jobStore.dontSetNonManagedTXConnectionAutoCommitFalse = DONT_TURN_OFF_AUTO_COMMIT

    # org.quartz.jobStore.txIsolationLevelReadCommitted = READ_COMMITTED_ISOLATION

    #

    #

    # 或者,使用一个用户自定义JobStore实现:

    #

    # org.quartz.jobStore.class = com.mycompany.goo.FooJobStore

    # org.quartz.jobStore.somePropOfFooJobStore = someValue

    #

    #

Java 主从 实现 集群 java quartz 集群_数据库_04

 

 

Java 主从 实现 集群 java quartz 集群_Source_05

Java 主从 实现 集群 java quartz 集群_Source_06


                    3.1.2.4 数据源的配置  

                        

# (只有当使用JDBCJobStore时需要, 或者一个插件需要JDBC)

    # -- 如果你的Scheduler非常忙碌,比如在一定的线程池内执行相同数目的任务,那么你应让数据源的连接数等于线程数 + 1

    #

    # 数据源定义的一般模式如下:

    #

    # org.quartz.dataSource.NAME.driver = DRIVER_CLASS_NAME

    # org.quartz.dataSource.NAME.URL = DB_URL

    # org.quartz.dataSource.NAME.user = DB_USER

    # org.quartz.dataSource.NAME.password = DB_PASSWORD

    # org.quartz.dataSource.NAME.maxConnections = DB_POOL_SIZE

    # org.quartz.dataSource.NAME.validationQuery= VALIDATION_QUERY

    #

    # or

    #

    # org.quartz.dataSource.NAME.jndiURL = DB_JNDI_URL

    #

    # or

    # org.quartz.dataSource.NAME.jndiURL = DB_JNDI_URL

    # org.quartz.dataSource.NAME.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP

    # org.quartz.dataSource.NAME.java.naming.factory.initial = JNDI_CTXT_FACTORY

    # org.quartz.dataSource.NAME.java.naming.provider.url = JNDI_PROVIDER_URL

    # org.quartz.dataSource.NAME.java.naming.security.principal = JNDI_PRINCIPAL

    # org.quartz.dataSource.NAME.java.naming.security.credentials = JNDI_CREDENTIALS

    #

    #

      上面显示了两种数据源定义方式:一个数据源可以用给定的数据库连接信息创建,也可以是利用应用服务器管理生成的JNDI数据源的逻辑映射。 


Java 主从 实现 集群 java quartz 集群_数据库_07


Java 主从 实现 集群 java quartz 集群_数据库_08


            


                    3.1.2.5 Scheduler插件的配置

                        

    # SchedulerPlugin定义的一般模式如下:
    #
    # org.quartz.plugin.NAME.class = PLUGIN_CLASS_NAME
    #
    # 如果这个插件类有一些属性值需要通过"setter"方法设定, 名称和值的属性定义如下:
    #
    # org.quartz.plugin.NAME.propName = propValue
    #
    # ..."propName" 在插件类中会有一个"setPropName"方法.但是只支持原始数据类型(包括 Strings)。
    #


      配置插件的简单示例:    

                        

org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin

    org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}

    org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger 
{1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} 
with resulting trigger instruction code: {9}

    

    org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin

    org.quartz.plugin.jobInitializer.fileName = data/my_job_data.xml

    org.quartz.plugin.jobInitializer.overWriteExistingJobs = false

    org.quartz.plugin.jobInitializer.failOnFileNotFound = true

    

    org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin

    org.quartz.plugin.shutdownhook.cleanShutdown = true

3.1.3 示例

#============================================================    # Configure Main Scheduler Properties    #===========================================================        org.quartz.scheduler.instanceName = MyClusteredScheduler
    org.quartz.scheduler.instanceId = AUTO
    
    #===========================================================
    # Configure ThreadPool
    #===========================================================
    
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount = 25
    org.quartz.threadPool.threadPriority = 5
    
    #===========================================================
    # Configure JobStore
    #===========================================================
    
    org.quartz.jobStore.misfireThreshold = 60000
    
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
    org.quartz.jobStore.useProperties = false
    org.quartz.jobStore.dataSource = myDS
    org.quartz.jobStore.tablePrefix = QRTZ_
    
    org.quartz.jobStore.isClustered = true
    org.quartz.jobStore.clusterCheckinInterval = 20000
    
    #===========================================================
    # Configure Datasources
    #===========================================================
    
    org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
    org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@cluster:1521:dev
    org.quartz.dataSource.myDS.user = quartz
    org.quartz.dataSource.myDS.password = quartz
    org.quartz.dataSource.myDS.maxConnections = 5
    org.quartz.dataSource.myDS.validationQuery=select 0 from dual

创建数据库

DROP TABLE IF EXISTS QRTZ_JOB_LISTENERS;DROP TABLE IF EXISTS QRTZ_TRIGGER_LISTENERS;DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;


CREATE TABLE QRTZ_JOB_DETAILS
(
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE VARCHAR(1) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
IS_STATEFUL VARCHAR(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_JOB_LISTENERS
(
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
JOB_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (JOB_NAME,JOB_GROUP,JOB_LISTENER),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT(13) NULL,
PREV_FIRE_TIME BIGINT(13) NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT(13) NOT NULL,
END_TIME BIGINT(13) NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT(2) NULL,
JOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(JOB_NAME,JOB_GROUP)
);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT(7) NOT NULL,
REPEAT_INTERVAL BIGINT(12) NOT NULL,
TIMES_TRIGGERED BIGINT(7) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_CRON_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(200) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_BLOB_TRIGGERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BLOB NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);

CREATE TABLE QRTZ_TRIGGER_LISTENERS
(
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
TRIGGER_LISTENER VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_LISTENER),
FOREIGN KEY (TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP)
);


CREATE TABLE QRTZ_CALENDARS
(
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BLOB NOT NULL,
PRIMARY KEY (CALENDAR_NAME)
);



CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (TRIGGER_GROUP)
);

CREATE TABLE QRTZ_FIRED_TRIGGERS
(
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
IS_VOLATILE VARCHAR(1) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT(13) NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_STATEFUL VARCHAR(1) NULL,
REQUESTS_RECOVERY VARCHAR(1) NULL,
PRIMARY KEY (ENTRY_ID)
);

CREATE TABLE QRTZ_SCHEDULER_STATE
(
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
CHECKIN_INTERVAL BIGINT(13) NOT NULL,
PRIMARY KEY (INSTANCE_NAME)
);

CREATE TABLE QRTZ_LOCKS
(
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (LOCK_NAME)
);


INSERT INTO QRTZ_LOCKS values('TRIGGER_ACCESS');
INSERT INTO QRTZ_LOCKS values('JOB_ACCESS');
INSERT INTO QRTZ_LOCKS values('CALENDAR_ACCESS');
INSERT INTO QRTZ_LOCKS values('STATE_ACCESS');
INSERT INTO QRTZ_LOCKS values('MISFIRE_ACCESS');


commit;