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

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

20191223202217702.png

概述


在默认情况下,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数据表解释

aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcxMDExMTA1MjA5NTg2.png


配置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.JobStoreTX 或 org.quartz.impl.jdbcjobstore.JobStoreCMT


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


示例


aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcxMDExMTA1NzAwODAz.png


配置文件:

#============================================================================
# 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数据:


aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcxMDEzMDU0MDA5Mzgx.png


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

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

相关文章
|
3月前
|
SQL Oracle 关系型数据库
Entity Framework Core 实现多数据库支持超厉害!配置连接、迁移与事务,开启多元数据库之旅!
【8月更文挑战第31天】在现代软件开发中,为了满足不同业务需求及环境要求,常需支持多个数据库系统。Entity Framework Core(EF Core)作为一款强大的对象关系映射(ORM)框架,通过数据库提供程序与多种数据库如SQL Server、MySQL、PostgreSQL、Oracle等交互。开发者可通过安装相应NuGet包并配置`DbContextOptionsBuilder`来指定不同数据库连接,从而实现多数据库支持。
212 0
|
6月前
|
Java 数据处理 调度
更高效准确的数据库内部任务调度实践,阿里云数据库SelectDB 内核 Apache Doris 内置 Job Scheduler 的实现与应用
Apache Doris 2.1 引入了内置的 Job Scheduler,旨在解决依赖外部调度系统的问题,提供秒级精确的定时任务管理。
|
存储 Java 关系型数据库
分布式定时任务框架Quartz总结和实践(2)—持久化到Mysql数据库
本文主要介绍分布式定时任务框架Quartz集成SpringBoot持久化数据到Mysql数据库的操作,上一篇文章使用Quartz创建定时任务都是保存在内存中,如果服务重启定时任务就会失效,所以Quartz官方也提供将定时任务等信息持久化到Mysql数据库的功能,本文主要实现这种Quartz的这种使用方式。
912 0
分布式定时任务框架Quartz总结和实践(2)—持久化到Mysql数据库
quartz(二)动态增删改查停止启用job
quartz(二)动态增删改查停止启用job
64 0
|
存储 Java 数据库连接
quartz-2.2.3集群部署使用到的数据库字段说明
quartz-2.2.3集群部署使用到的数据库字段说明
243 0
|
Java 数据库 Spring
Quartz的持久化数据库(九)中
Quartz的持久化数据库(九)中
748 0
Quartz的持久化数据库(九)中
|
存储 前端开发 Java
Quartz的持久化数据库(九)下
Quartz的持久化数据库(九)下
244 0
Quartz的持久化数据库(九)下
|
存储 Java 数据库连接
Quartz的持久化数据库(九)上
Quartz的持久化数据库(九)上
479 0
Quartz的持久化数据库(九)上
|
SQL 调度 数据库
Quartz.NET开源作业调度框架系列(五):AdoJobStore保存job到数据库
在Quartz.NET中主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)。对于同一个 trigger 来说,有状态的 job 不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。一个 job 可以被多个 trigger 关联,但是一个 trigger 只能关联一个 job。某些任务需要对数据库中的数据进行增删改处理 , 这些任务不能并发执行,就需要用到无状态的任务 , 否则会造成数据混乱。
667 0
Quartz.NET开源作业调度框架系列(五):AdoJobStore保存job到数据库
|
存储 关系型数据库 调度
Quartz任务调度(3)存储与持久化操作配置详细解析
<div class="markdown_views"> <h1 id="内存存储ramjobstore">内存存储RAMJobStore</h1> <p>Quartz默认使用RAMJobStore,它的优点是速度。因为所有的 Scheduler 信息都保存在计算机内存中,访问这些数据随着电脑而变快。而无须访问数据库或IO等操作,但它的缺点是将 Job 和 Trigger 信
17404 0