1、原理:集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。目前集群只能工作在JDBC-JobStore(JobStore TX或者JobStoreCMT)方式下,从本质上来说,是使集群上的每一个节点通过共享同一个数据库来工作的( Quartz通过启动两个维护线程来维护数据库状态实现集群管理,一个是检测节点状态线程,一个是恢复任务线程 )。负载平衡是自动完成的,集群的每个节点会尽快触发任务。当一个触发器的触发时间到达时,第一个节点将会获得任务(通过锁定),成为执行任务的节点。故障切换的发生是在当一个节点正在执行一个或者多个任务失败的时候。当一个节点失败了,其他的节点会检测到并且标 识在失败节点上正在进行的数据库中的任务。任何被标记为可恢复(任务详细信息的"requests recovery"属性)的任务都会被其他的节点重新执行。没有标记可恢复的任务只会被释放出来,将会在下次相关触发器触发时执行。2、集群管理用到的表:
--任务详细信息表
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)
);
不同的线程池大小,
不同的"org.quartz.scheduler.instanceId"属性值(这个可以很容易做到,设定为"AUTO"即可)。
注意: 永远不要在不同的机器上运行集群,除非他们的时钟是使用某种形式的同步服务(守护)非常有规律的运行(时钟必须在一分一秒内)来达到同步。还有: 永远不要触发一个非集群的实例,如果其他的实例正在同一个数据库表上运行。你将使你的数据严重腐蚀,出现非预期行为。
示例及详细配置说明,请参照附录Quartz配置文件说明。
# 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
# 定制一个线程池的一般模式如下:
#
# 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
#
# 定义一个任务存储的一般模式如下:
#
# 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
#
#
# (只有当使用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
#
#
# 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
#============================================================ # 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;