Quartz-任务调度信息持久化到DB中


在这里插入图片描述

概述

在默认情况下,Quartz将任务调度的运行信息保存在内存中。 这种方法提供了最佳的性能,因为在内存中的数据访问速度最快;不足之处就是却反数据的持久性,当程序中途停止或者系统崩溃时,所有运行的信息都会丢失。

比如我们希望安排一个执行100次的任务,如果执行到50次时系统崩溃了,系统重启时任务的执行计数器将从0开始。在大多数实际的应用中,我们往往并不需要保存任务调度的现场数据,因为很少需要规划一个指定执行次数的任务。对于仅执行一次的任务来说,其执行条件信息本身应该是已经持久化的业务数据,当执行完成后,条件信息也会相应改变。当然调度现场信息不仅仅是记录运行次数,还包括调度规则、JobDataMap中的数据等等。

如果确实需要持久化任务调度信息,Quartz允许你通过调整其属性文件,将这些信息保存到数据库中。使用数据库保存任务调度信息后,即使系统崩溃后重新启动,任务的调度信息将得到恢复。如前面所说的例子,执行50次崩溃后重新运行,计数器将从51开始计数。使用了数据库保存信息的任务称为持久化任务。


操作步骤

执行脚本建立对应的表

方式一: 从下载的压缩包中找 quartz-2.2.3\docs\dbTables中

方式二:https://github.com/quartz-scheduler/quartz/tree/quartz-1.8.x/docs/dbTables

这里我们使用Oracle数据库,我们执行tables_oracle.sql即可,共计11个表。

quartz数据表解释

这里写图片描述


配置quartz.properties

首先,我们需要在我们的属性文件中表明使用JobStoreTX:

org.quartz.jobStore.class = org.quartz.ompl.jdbcjobstore.JobStoreTX

然后我们需要配置能理解不同数据库系统中某一特定方言的驱动代理,选择选择对应数据库的代理类。

这些代理类在org.quar.impl.jdbcjobstore package或者其子下。
包括

  • DB2v6Delegate (for DB2 version 6 and earlier)
  • HSQLDBDelegate (for HSQLDB)
  • MSSQLDelegate (for Microsoft SQLServer)
  • PostgreSQLDelegate (for PostgreSQL)
  • WeblogicDelegate (for using JDBC drivers made by WebLogic)
  • OracleDelegate (for using Oracle)等

然后确定应用程序需要哪种类型的事务。 如果不需要将调度命令(例如添加和删除触发器)绑定到其他事务,那么可以通过使用JobStoreTX作为JobStore来管理事务(这是最常见的选择)。

如果需要Quartz与其他事务(即在J2EE应用程序服务器中)一起工作,那么您应该使用JobStoreCMT - 在这种情况下,Quartz将让应用程序服务器容器管理事务。

最后设置一个DataSource,JDBCJobStore可以从中获取与数据库的连接。 DataSources在Quartz属性中使用几种不同的方法之一进行定义。

  • 一种方法是让Quartz创建和管理DataSource本身 - 通过提供数据库的所有连接信息。
  • 另一种方法是让Quartz使用由Quartz正在运行的应用程序服务器管理的DataSource,通过提供JDBCJobStore DataSource的JNDI名称

要使用JDBCJobStore(并假定使用的是StdSchedulerFactory),首先需要将Quartz配置的JobStore类属性设置为org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.impl.jdbcjobstore.JobStoreCMT

org.quartz.jobStore.useProperties配置参数设置为“true”(默认为false),以表示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称 - 值对存储 而不是在BLOB列中以其序列化形式存储更多复杂的对象。 从长远来看,这是更安全的,因为避免了将非String类序列化为BLOB的类版本问题。


示例

这里写图片描述

配置文件:

#============================================================================
# Configure Main Scheduler Properties  
#============================================================================

org.quartz.scheduler.instanceName: ArtisanScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.scheduler.skipUpdateCheck: true

org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false


#============================================================================
# Configure ThreadPool  
#============================================================================

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 3
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true


#============================================================================
# Configure JobStore  
# (choose the right driverDelegateClass, for oracle:OracleDelegate ,for mysql:StdJDBCDelegate)
#============================================================================

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.dataSource : qzDS
org.quartz.jobStore.tablePrefix : QRTZ_
org.quartz.jobStore.useProperties : true
org.quartz.jobStore.isClustered: false

#============================================================================
# Configure Datasource  ORACLE
#============================================================================


org.quartz.dataSource.qzDS.connectionProvider.class : com.xgj.quartz.quartzItself.saveInfoInDB.MyPoolingconnectionProvider

org.quartz.dataSource.qzDS.driverClassName : oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.qzDS.url : jdbc:oracle:thin:@172.25.246.11:1521:testbed
org.quartz.dataSource.qzDS.username : cc
org.quartz.dataSource.qzDS.password : xxxx




Job

package com.xgj.quartz.quartzItself.saveInfoInDB;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;

public class MyPersistenceJob implements Job {

	@Override
	public void execute(JobExecutionContext context)
			throws JobExecutionException {

		JobKey jobKey = context.getJobDetail().getKey();

		System.out.println("\n任务key "
				+ jobKey
				+ "执行时间:"
				+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
						.format(new Date()));
	}

}

数据源连接

package com.xgj.quartz.quartzItself.saveInfoInDB;

import java.sql.Connection;
import java.sql.SQLException;

import org.apache.commons.dbcp.BasicDataSource;
import org.quartz.utils.ConnectionProvider;

/**
 * 
 * 
 * @ClassName: MyPoolingconnectionProvider
 * 
 * @Description: MyPoolingconnectionProvider数据源连接和quartz.properties配置文件。
 * 
 *               数据源是自己定义的类,实现了quartz自带的ConnectionProvider类,如果不想使用它,
 *               你也可以选择其他数据源,
 *               比如Tomcat的DataSource,Spring的SimpleDriverDataSource等,自行选择.
 * 
 *               DBCP数据源连接池的属性,这里仅仅使用了必须的配置,其他配置也显式设置,也可使用默认值,根据需要执行调整。
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年10月10日 下午9:49:07
 */
public class MyPoolingconnectionProvider implements ConnectionProvider {
	/**
	 * 使用apache dbcp数据源连接池
	 */
	private BasicDataSource datasource = new BasicDataSource();
	private String driverClassName;
	private String url;
	private String username;
	private String password;

	@Override
	public Connection getConnection() throws SQLException {
		System.out.println("getConnection");
		return datasource.getConnection();
	}

	@Override
	public void shutdown() throws SQLException {
		System.out.println("connection pool shutdown");
		datasource.close();

	}

	@Override
	public void initialize() throws SQLException {
		try {
			System.out.println("inti connection");
			datasource.setDriverClassName(driverClassName);
			datasource.setUrl(url);
			datasource.setUsername(username);
			datasource.setPassword(password);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void setDatasource(BasicDataSource datasource) {
		this.datasource = datasource;
	}

	public void setDriverClassName(String driverClassName) {
		this.driverClassName = driverClassName;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

测试类

package com.xgj.quartz.quartzItself.saveInfoInDB;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SchedulerMetaData;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.triggers.SimpleTriggerImpl;

public class QuartzPersistenceTest {

	public static void main(String[] args) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

		try {

			System.out.println("------- 初始化 ----------------------");

			// 通过调度器工厂获取调度器,初始化工程时须指定其使用我们自己的配置文件
			SchedulerFactory sf = new StdSchedulerFactory(
					"quartz/quartz.properties");
			Scheduler sched = sf.getScheduler();

			// clear一下,因为使用数据库储存方式时,shutdown的时候没有清除,第二次运行会报Job is already
			// exist
			sched.clear();

			System.out.println("------- 初始化完成 -----------");

			// 下一分钟开始执行
			Date runTime = DateBuilder.evenMinuteDate(new Date());

			System.out.println("------- Scheduling Job  -------------------");

			// 任务详情
			JobDetail job = JobBuilder.newJob(MyPersistenceJob.class)
					.withIdentity("artisanJob", "artisanGroup").build();

			// 触发器 重复20+1次 间隔2秒
			SimpleTriggerImpl trigger = (SimpleTriggerImpl) TriggerBuilder
					.newTrigger()
					.withIdentity("artisanTrigger", "artisanGroup")
					.startAt(runTime).build();
			trigger.setRepeatCount(20);
			trigger.setRepeatInterval(2000);

			System.out.println("------- 当前时间:" + sdf.format(new Date())
					+ " -----------------");

			// 调度器、触发器、任务,三者关联
			sched.scheduleJob(job, trigger);

			System.out.println(job.getKey() + " 开始job运行时间:"
					+ sdf.format(runTime));

			// 调度启动
			sched.start();
			System.out.println("------- 开始调度器 Scheduler -----------------");

			System.out.println("------- 等待10秒-------------");
			try {
				Thread.sleep(1 * 10000L);
			} catch (Exception e) {
			}

			System.out.println("------- 关闭调度器 模拟异常退出---------------------");
			sched.shutdown(true);

			System.out.println("------- 异常退出 -----------------");

			SchedulerMetaData metaData = sched.getMetaData();
			System.out.println("目前执行了 "
					+ metaData.getNumberOfJobsExecuted() + " 个 jobs.");
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

通过关闭quartz模拟异常中断的场景

第一次运行日志:

------- 初始化 ----------------------
inti connection
INFO  StdSchedulerFactory - Using default implementation for ThreadExecutor
INFO  SimpleThreadPool - Job execution threads will use class loader of thread: main
INFO  SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
INFO  QuartzScheduler - Quartz Scheduler v.2.2.3 created.
INFO  JobStoreTX - Using thread monitor-based data access locking (synchronization).
INFO  JobStoreTX - JobStoreTX initialized.
INFO  QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.3) 'ArtisanScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 3 threads.
  Using job-store 'org.quartz.impl.jdbcjobstore.JobStoreTX' - which supports persistence. and is not clustered.

INFO  StdSchedulerFactory - Quartz scheduler 'ArtisanScheduler' initialized from the specified file : 'quartz/quartz.properties' from the class resource path.
INFO  StdSchedulerFactory - Quartz scheduler version: 2.2.3
getConnection
------- 初始化完成 -----------
------- Scheduling Job  -------------------
------- 当前时间:2017-10-12 17:35:53 -----------------
getConnection
artisanGroup.artisanJob 开始job运行时间:2017-10-12 17:36:00
getConnection
INFO  JobStoreTX - Freed 0 triggers from 'acquired' / 'blocked' state.
INFO  JobStoreTX - Recovering 0 jobs that were in-progress at the time of the last shut-down.
INFO  JobStoreTX - Recovery complete.
INFO  JobStoreTX - Removed 0 'complete' triggers.
INFO  JobStoreTX - Removed 0 stale fired job entries.
INFO  QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED started.
getConnection
------- 开始调度器 Scheduler -----------------
------- 等待10-------------
getConnection
getConnection
getConnection

任务key artisanGroup.artisanJob执行时间:2017-10-12 17:36:00
getConnection
getConnection
getConnection

任务key artisanGroup.artisanJob执行时间:2017-10-12 17:36:02
getConnection
------- 关闭调度器 模拟异常退出---------------------
INFO  QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED shutting down.
INFO  QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED paused.
connection pool shutdown
INFO  QuartzScheduler - Scheduler ArtisanScheduler_$_NON_CLUSTERED shutdown complete.
------- 异常退出 -----------------
目前执行了 2 个 jobs.


数据库表qrtz_simple_triggers数据:

这里写图片描述

将测试类中的下列代码屏蔽,使其正常运行

System.out.println("------- 等待10秒-------------");
			try {
				Thread.sleep(1 * 10000L);
			} catch (Exception e) {
			}

			System.out.println("------- 关闭调度器 模拟异常退出---------------------");
			sched.shutdown(true);

			System.out.println("------- 异常退出 -----------------");

再次运行

qrtz_simple_triggers 中的 times_triggered字段重0开始计算, 执行完成后,该表的数据为空。


总结

简单的10个字概括就是:未执行,插入; 执行过,删除。

示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值