1. 程式人生 > >quartz (從原理到應用)詳解篇

quartz (從原理到應用)詳解篇

一、Quartz 基本介紹

           1.1 Quartz 概述

           1.2 Quartz特點

           1.3 Quartz 叢集配置

    二、Quartz 原理及流程

           2.1 quartz基本原理

           2.2 quartz啟動流程

   三、Spring + Quartz 實現企業級排程的實現示例

          3.1 環境資訊

          3.2 相關程式碼及配置

    四、問題及解決方案

    五、相關知識

    六、參考資料

     總結

一、Quartz 基本介紹

    1.1 Quartz 概述

           Quartz 是 OpenSymphony 開源組織在任務排程領域的一個開源專案,完全基於 Java 實現。該專案於 2009 年被 Terracotta 收購,目前是 Terracotta 旗下的一個專案。讀者可以到 

http://www.quartz-scheduler.org/站點下載 Quartz 的釋出版本及其原始碼。

   1.2 Quartz特點

       作為一個優秀的開源排程框架,Quartz 具有以下特點:

  1. 強大的排程功能,例如支援豐富多樣的排程方法,可以滿足各種常規及特殊需求;
  2. 靈活的應用方式,例如支援任務和排程的多種組合方式,支援排程資料的多種儲存方式;
  3. 分散式和叢集能力,Terracotta 收購後在原來功能基礎上作了進一步提升。

      另外,作為 Spring 預設的排程框架,Quartz 很容易與 Spring 整合實現靈活可配置的排程功能。

    quartz排程核心元素

  1. Scheduler:任務排程器,是實際執行任務排程的控制器。在spring中通過SchedulerFactoryBean封裝起來。
  2. Trigger:觸發器,用於定義任務排程的時間規則,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比較多,本文主要介紹這種方式。CronTrigger在spring中封裝在CronTriggerFactoryBean中。
  3. Calendar:它是一些日曆特定時間點的集合。一個trigger可以包含多個Calendar,以便排除或包含某些時間點。
  4. JobDetail:用來描述Job實現類及其它相關的靜態資訊,如Job名字、關聯監聽器等資訊。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean兩種實現,如果任務排程只需要執行某個類的某個方法,就可以通過MethodInvokingJobDetailFactoryBean來呼叫。
  5. Job:是一個介面,只有一個方法void execute(JobExecutionContext context),開發者實現該介面定義執行任務,JobExecutionContext類提供了排程上下文的各種資訊。Job執行時的資訊儲存在JobDataMap例項中。實現Job介面的任務,預設是無狀態的,若要將Job設定成有狀態的,在quartz中是給實現的Job新增@DisallowConcurrentExecution註解(以前是實現StatefulJob介面,現在已被Deprecated),在與spring結合中可以在spring配置檔案的job detail中配置concurrent引數。

  1.3 Quartz 叢集配置

quartz叢集是通過資料庫表來感知其他的應用的,各個節點之間並沒有直接的通訊。只有使用持久的JobStore才能完成Quartz叢集。
資料庫表:以前有12張表,現在只有11張表,現在沒有儲存listener相關的表,多了QRTZ_SIMPROP_TRIGGERS表:

Table nameDescription
QRTZ_CALENDARS儲存Quartz的Calendar資訊
QRTZ_CRON_TRIGGERS儲存CronTrigger,包括Cron表示式和時區資訊
QRTZ_FIRED_TRIGGERS儲存與已觸發的Trigger相關的狀態資訊,以及相聯Job的執行資訊
QRTZ_PAUSED_TRIGGER_GRPS儲存已暫停的Trigger組的資訊
QRTZ_SCHEDULER_STATE儲存少量的有關Scheduler的狀態資訊,和別的Scheduler例項
QRTZ_LOCKS儲存程式的悲觀鎖的資訊
QRTZ_JOB_DETAILS儲存每一個已配置的Job的詳細資訊
QRTZ_SIMPLE_TRIGGERS儲存簡單的Trigger,包括重複次數、間隔、以及已觸的次數
QRTZ_BLOG_TRIGGERSTrigger作為Blob型別儲存
QRTZ_TRIGGERS儲存已配置的Trigger的資訊
QRTZ_SIMPROP_TRIGGERS

QRTZ_LOCKS就是Quartz叢集實現同步機制的行鎖表,包括以下幾個鎖:CALENDAR_ACCESS 、JOB_ACCESS、MISFIRE_ACCESS 、STATE_ACCESS 、TRIGGER_ACCESS。

  二、Quartz 原理及流程

      2.1 quartz基本原理

核心元素

Quartz 任務排程的核心元素是 scheduler, trigger 和 job,其中 trigger 和 job 是任務排程的元資料, scheduler 是實際執行排程的控制器。

在 Quartz 中,trigger 是用於定義排程時間的元素,即按照什麼時間規則去執行任務。Quartz 中主要提供了四種類型的 trigger:SimpleTrigger,CronTirgger,DateIntervalTrigger,和 NthIncludedDayTrigger。這四種 trigger 可以滿足企業應用中的絕大部分需求。我們將在企業應用一節中進一步討論四種 trigger 的功能。

在 Quartz 中,job 用於表示被排程的任務。主要有兩種型別的 job:無狀態的(stateless)和有狀態的(stateful)。對於同一個 trigger 來說,有狀態的 job 不能被並行執行,只有上一次觸發的任務被執行完之後,才能觸發下一次執行。Job 主要有兩種屬性:volatility 和 durability,其中 volatility 表示任務是否被持久化到資料庫儲存,而 durability 表示在沒有 trigger 關聯的時候任務是否被保留。兩者都是在值為 true 的時候任務被持久化或保留。一個 job 可以被多個 trigger 關聯,但是一個 trigger 只能關聯一個 job。

在 Quartz 中, scheduler 由 scheduler 工廠建立:DirectSchedulerFactory 或者 StdSchedulerFactory。 第二種工廠 StdSchedulerFactory 使用較多,因為 DirectSchedulerFactory 使用起來不夠方便,需要作許多詳細的手工編碼設定。 Scheduler 主要有三種:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。本文以最常用的 StdScheduler 為例講解。這也是筆者在專案中所使用的 scheduler 類。

Quartz 核心元素之間的關係如下圖所示:

圖 1. Quartz 核心元素關係圖

圖 1. Quartz 核心元素關係圖

執行緒檢視

在 Quartz 中,有兩類執行緒,Scheduler 排程執行緒和任務執行執行緒,其中任務執行執行緒通常使用一個執行緒池維護一組執行緒。

圖 2. Quartz 執行緒檢視

圖 2. Quartz 執行緒檢視

Scheduler 排程執行緒主要有兩個: 執行常規排程的執行緒,和執行 misfired trigger 的執行緒。常規排程執行緒輪詢儲存的所有 trigger,如果有需要觸發的 trigger,即到達了下一次觸發的時間,則從任務執行執行緒池獲取一個空閒執行緒,執行與該 trigger 關聯的任務。Misfire 執行緒是掃描所有的 trigger,檢視是否有 misfired trigger,如果有的話根據 misfire 的策略分別處理。下圖描述了這兩個執行緒的基本流程:

圖 3. Quartz 排程執行緒流程圖

圖 3. Quartz 排程執行緒流程圖

關於 misfired trigger,我們在企業應用一節中將進一步描述。

資料儲存

Quartz 中的 trigger 和 job 需要儲存下來才能被使用。Quartz 中有兩種儲存方式:RAMJobStore, JobStoreSupport,其中 RAMJobStore 是將 trigger 和 job 儲存在記憶體中,而 JobStoreSupport 是基於 jdbc 將 trigger 和 job 儲存到資料庫中。RAMJobStore 的存取速度非常快,但是由於其在系統被停止後所有的資料都會丟失,所以在通常應用中,都是使用 JobStoreSupport。

在 Quartz 中,JobStoreSupport 使用一個驅動代理來操作 trigger 和 job 的資料儲存:StdJDBCDelegate。StdJDBCDelegate 實現了大部分基於標準 JDBC 的功能介面,但是對於各種資料庫來說,需要根據其具體實現的特點做某些特殊處理,因此各種資料庫需要擴充套件 StdJDBCDelegate 以實現這些特殊處理。Quartz 已經自帶了一些資料庫的擴充套件實現,可以直接使用,如下圖所示:

圖 4. Quartz 資料庫驅動代理

圖 4. Quartz 資料庫驅動代理

作為嵌入式資料庫的代表,Derby 近來非常流行。如果使用 Derby 資料庫,可以使用上圖中的 CloudscapeDelegate 作為 trigger 和 job 資料儲存的代理類。

   2.2 quartz啟動流程

若quartz是配置在spring中,當伺服器啟動時,就會裝載相關的bean。SchedulerFactoryBean實現了InitializingBean介面,因此在初始化bean的時候,會執行afterPropertiesSet方法,該方法將會呼叫SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,通常用StdSchedulerFactory)建立Scheduler。SchedulerFactory在建立quartzScheduler的過程中,將會讀取配置引數,初始化各個元件,關鍵元件如下:

  1. ThreadPool:一般是使用SimpleThreadPool,SimpleThreadPool建立了一定數量的WorkerThread例項來使得Job能夠線上程中進行處理。WorkerThread是定義在SimpleThreadPool類中的內部類,它實質上就是一個執行緒。在SimpleThreadPool中有三個list:workers-存放池中所有的執行緒引用,availWorkers-存放所有空閒的執行緒,busyWorkers-存放所有工作中的執行緒;
    執行緒池的配置引數如下所示:

    1
    2
    3
    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount=3
    org.quartz.threadPool.threadPriority=5
  2. JobStore:分為儲存在記憶體的RAMJobStore和儲存在資料庫的JobStoreSupport(包括JobStoreTX和JobStoreCMT兩種實現,JobStoreCMT是依賴於容器來進行事務的管理,而JobStoreTX是自己管理事務),若要使用叢集要使用JobStoreSupport的方式;

  3. QuartzSchedulerThread:用來進行任務排程的執行緒,在初始化的時候paused=true,halted=false,雖然執行緒開始運行了,但是paused=true,執行緒會一直等待,直到start方法將paused置為false;

另外,SchedulerFactoryBean還實現了SmartLifeCycle介面,因此初始化完成後,會執行start()方法,該方法將主要會執行以下的幾個動作:

  1. 建立ClusterManager執行緒並啟動執行緒:該執行緒用來進行叢集故障檢測和處理,將在下文詳細討論;
  2. 建立MisfireHandler執行緒並啟動執行緒:該執行緒用來進行misfire任務的處理,將在下文詳細討論;
  3. 置QuartzSchedulerThread的paused=false,排程執行緒才真正開始排程;

整個啟動流程如下圖:
quartz啟動時序圖

 三、Spring + Quartz 實現企業級排程的實現示例

3.1 環境資訊

   此示例中的環境: Spring 4.1.6.RELEASE   + quartz 2.2.1 + Mysql 5.6

3.2 相關程式碼及配置

   3.2.1 Maven 引入

       

 3.2.2 資料庫指令碼準備

SET FOREIGN_KEY_CHECKS=0;

– —————————-
– Table structure for task_schedule_job
– —————————-
DROP TABLE IF EXISTS `task_schedule_job`;
CREATE TABLE `task_schedule_job` (
  `job_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `create_time` timestamp NULL DEFAULT NULL,
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `job_name` varchar(255) DEFAULT NULL,
  `job_group` varchar(255) DEFAULT NULL,
  `job_status` varchar(255) DEFAULT NULL,
  `cron_expression` varchar(255) NOT NULL,
  `description` varchar(255) DEFAULT NULL,
  `bean_class` varchar(255) DEFAULT NULL,
  `is_concurrent` varchar(255) DEFAULT NULL COMMENT ‘1’,
  `spring_id` varchar(255) DEFAULT NULL,
  `method_name` varchar(255) NOT NULL
  PRIMARY KEY (`job_id`),
  UNIQUE KEY `name_group` (`job_name`,`job_group`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

在Quartz包下docs/dbTables,選擇對應的資料庫指令碼,建立相應的資料庫表即可,我用的是mysql5.6,這裡有一個需要注意的地方,mysql5.5之前用的表儲存引擎是MyISAM,使用的是表級鎖,鎖發生衝突的概率比較高,併發度低;5.6之後預設的儲存引擎為InnoDB,InnoDB採用的鎖機制是行級鎖,併發度也較高。而quartz叢集使用資料庫鎖的

機制來來實現同一個任務在同一個時刻只被例項執行,所以為了防止衝突,我們建表的時候要選取InnoDB作為表的存

儲引擎。如下:

      

 3.2.3 關鍵程式碼及配置

   <1>spring-quartz.xml 配置 在application.xml 檔案中引入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- 註冊本地排程任務
    <bean id="localQuartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"></bean>-->

    <!-- 註冊叢集排程任務 -->
    <bean id="schedulerFactoryBean" lazy-init="false" autowire="no"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean" destroy-method="destroy">
        <!--可選,QuartzScheduler 啟動時更新己存在的Job,這樣就不用每次修改targetObject後刪除qrtz_job_details表對應記錄了 -->
        <property name="overwriteExistingJobs" value="true" />
        <!--必須的,QuartzScheduler 延時啟動,應用啟動完後 QuartzScheduler 再啟動 -->
        <property name="startupDelay" value="3" />
        <!-- 設定自動啟動 -->
        <property name="autoStartup" value="true" />
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        <property name="configLocation" value="classpath:quartz.properties" />
    </bean>

</beans>

<2>quartz.properties 檔案配置

#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = KuanrfGSQuartzScheduler
org.quartz.scheduler.instanceId = AUTO

#==============================================================
#Configure JobStore
#==============================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.maxMisfiresToHandleAtATime = 1
org.quartz.jobStore.misfireThreshold = 120000
org.quartz.jobStore.txIsolationLevelSerializable = false

#==============================================================
#Configure DataSource
#==============================================================
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = 你的資料鏈接
org.quartz.dataSource.myDS.user = 使用者名稱
org.quartz.dataSource.myDS.password = 密碼
org.quartz.dataSource.myDS.maxConnections = 30
org.quartz.jobStore.selectWithLockSQL = SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE

#==============================================================
#Configure ThreadPool
#==============================================================
org.quartz.threadPool.class= org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount= 10
org.quartz.threadPool.threadPriority= 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true

#==============================================================
#Skip Check Update
#update:true
#not update:false
#==============================================================
org.quartz.scheduler.skipUpdateCheck = true

#============================================================================
# Configure Plugins
#============================================================================
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownhook.cleanShutdown = true

<3>關鍵程式碼

package com.netease.ad.omp.service.sys;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import com.netease.ad.omp.common.utils.SpringUtils;
import com.netease.ad.omp.dao.sys.mapper.ScheduleJobMapper;
import com.netease.ad.omp.entity.sys.ScheduleJob;
import com.netease.ad.omp.quartz.job.JobUtils;
import com.netease.ad.omp.quartz.job.MyDetailQuartzJobBean;
import com.netease.ad.omp.quartz.job.QuartzJobFactory;
import com.netease.ad.omp.quartz.job.QuartzJobFactoryDisallowConcurrentExecution;
import org.apache.log4j.Logger;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Service;


/**
 * 計劃任務管理
 */
@Service
public class JobTaskService {
   public final Logger log = Logger.getLogger(this.getClass());
   @Autowired
   private SchedulerFactoryBean schedulerFactoryBean;

   @Autowired
   private ScheduleJobMapper scheduleJobMapper;


   /**
    * 從資料庫中取 區別於getAllJob
    * 
    * @return
    */
   public List<ScheduleJob> getAllTask() {
      return scheduleJobMapper.select(null);
   }

   /**
    * 新增到資料庫中 區別於addJob
    */
   public void addTask(ScheduleJob job) {
      job.setCreateTime(new Date());
      scheduleJobMapper.insertSelective(job);
   }

   /**
    * 從資料庫中查詢job
    */
   public ScheduleJob getTaskById(Long jobId) {
      return scheduleJobMapper.selectByPrimaryKey(jobId);
   }

   /**
    * 更改任務狀態
    * 
    * @throws SchedulerException
    */
   public void changeStatus(Long jobId, String cmd) throws SchedulerException {
      ScheduleJob job = getTaskById(jobId);
      if (job == null) {
         return;
      }
      if ("stop".equals(cmd)) {
         deleteJob(job);
         job.setJobStatus(JobUtils.STATUS_NOT_RUNNING);
      } else if ("start".equals(cmd)) {
         job.setJobStatus(JobUtils.STATUS_RUNNING);
         addJob(job);
      }
      scheduleJobMapper.updateByPrimaryKeySelective(job);
   }

   /**
    * 更改任務 cron表示式
    * 
    * @throws SchedulerException
    */
   public void updateCron(Long jobId, String cron) throws SchedulerException {
      ScheduleJob job = getTaskById(jobId);
      if (job == null) {
         return;
      }
      job.setCronExpression(cron);
      if (JobUtils.STATUS_RUNNING.equals(job.getJobStatus())) {
         updateJobCron(job);
      }
      scheduleJobMapper.updateByPrimaryKeySelective(job);
   }

   /**
    * 新增任務
    * 
    * @throws SchedulerException
    */
   public void addJob(ScheduleJob job) throws SchedulerException {
      if (job == null || !JobUtils.STATUS_RUNNING.equals(job.getJobStatus())) {
         return;
      }
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      log.debug(scheduler + ".......................................................................................add");
      TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());

      CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

      // 不存在,建立一個
      if (null == trigger) {
         Class clazz = JobUtils.CONCURRENT_IS.equals(job.getIsConcurrent()) ? QuartzJobFactory.class : QuartzJobFactoryDisallowConcurrentExecution.class;
         JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(job.getJobName(), job.getJobGroup()).build();

         jobDetail.getJobDataMap().put("scheduleJob", job);

         CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

         trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).build();

         scheduler.scheduleJob(jobDetail, trigger);
      } else {
         // Trigger已存在,那麼更新相應的定時設定
         CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());

         // 按新的cronExpression表示式重新構建trigger
         trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

         // 按新的trigger重新設定job執行
         scheduler.rescheduleJob(triggerKey, trigger);
      }
   }

   @PostConstruct
   public void init() throws Exception {

      // 這裡獲取任務資訊資料
      List<ScheduleJob> jobList = scheduleJobMapper.select(null);

      for (ScheduleJob job : jobList) {
         addJob(job);
      }
   }

   /**
    * 獲取所有計劃中的任務列表
    * 
    * @return
    * @throws SchedulerException
    */
   public List<ScheduleJob> getAllJob() throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
      Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
      List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
      for (JobKey jobKey : jobKeys) {
         List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
         for (Trigger trigger : triggers) {
            ScheduleJob job = new ScheduleJob();
            job.setJobName(jobKey.getName());
            job.setJobGroup(jobKey.getGroup());
            job.setDescription("觸發器:" + trigger.getKey());
            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
            job.setJobStatus(triggerState.name());
            if (trigger instanceof CronTrigger) {
               CronTrigger cronTrigger = (CronTrigger) trigger;
               String cronExpression = cronTrigger.getCronExpression();
               job.setCronExpression(cronExpression);
            }
            jobList.add(job);
         }
      }
      return jobList;
   }

   /**
    * 所有正在執行的job
    * 
    * @return
    * @throws SchedulerException
    */
   public List<ScheduleJob> getRunningJob() throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
      List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
      for (JobExecutionContext executingJob : executingJobs) {
         ScheduleJob job = new ScheduleJob();
         JobDetail jobDetail = executingJob.getJobDetail();
         JobKey jobKey = jobDetail.getKey();
         Trigger trigger = executingJob.getTrigger();
         job.setJobName(jobKey.getName());
         job.setJobGroup(jobKey.getGroup());
         job.setDescription("觸發器:" + trigger.getKey());
         Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
         job.setJobStatus(triggerState.name());
         if (trigger instanceof CronTrigger) {
            CronTrigger cronTrigger = (CronTrigger) trigger;
            String cronExpression = cronTrigger.getCronExpression();
            job.setCronExpression(cronExpression);
         }
         jobList.add(job);
      }
      return jobList;
   }

   /**
    * 暫停一個job
    * 
    * @param scheduleJob
    * @throws SchedulerException
    */
   public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
      scheduler.pauseJob(jobKey);
   }

   /**
    * 恢復一個job
    * 
    * @param scheduleJob
    * @throws SchedulerException
    */
   public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
      scheduler.resumeJob(jobKey);
   }

   /**
    * 刪除一個job
    * 
    * @param scheduleJob
    * @throws SchedulerException
    */
   public void deleteJob(ScheduleJob scheduleJob) throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
      scheduler.deleteJob(jobKey);

   }

   /**
    * 立即執行job
    * 
    * @param scheduleJob
    * @throws SchedulerException
    */
   public void runAJobNow(ScheduleJob scheduleJob) throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();
      JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
      scheduler.triggerJob(jobKey);
   }

   /**
    * 更新job時間表達式
    * 
    * @param scheduleJob
    * @throws SchedulerException
    */
   public void updateJobCron(ScheduleJob scheduleJob) throws SchedulerException {
      Scheduler scheduler = schedulerFactoryBean.getScheduler();

      TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());

      CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

      CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob.getCronExpression());

      trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

      scheduler.rescheduleJob(triggerKey, trigger);
   }

   public static void main(String[] args) {
      CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("xxxxx");
   }
}
package com.netease.ad.omp.quartz.job;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.netease.ad.omp.common.utils.SpringUtils;
import com.netease.ad.omp.entity.sys.ScheduleJob;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.springframework.context.ApplicationContext;

/**
 * Created with IntelliJ IDEA
 * ProjectName: omp
 * Author:  bjsonghongxu
 * CreateTime : 15:58
 * Email: [email protected]
 * Class Description:
 *   定時任務工具類
 */
public class JobUtils {
    public final static Logger log = Logger.getLogger(JobUtils.class);
    public static final String STATUS_RUNNING = "1"; //啟動狀態
    public static final String STATUS_NOT_RUNNING = "0"; //未啟動狀態
    public static final String CONCURRENT_IS = "1";
    public static final String CONCURRENT_NOT = "0";

    private ApplicationContext ctx;

    /**
     * 通過反射呼叫scheduleJob中定義的方法
     *
     * @param scheduleJob
     */
    public static void invokMethod(ScheduleJob scheduleJob,JobExecutionContext context) {
        Object object = null;
        Class clazz = null;
        if (StringUtils.isNotBlank(scheduleJob.getSpringId())) {
            object = SpringUtils.getBean(scheduleJob.getSpringId());
        } else if (StringUtils.isNotBlank(scheduleJob.getBeanClass())) {
            try {
                clazz = Class.forName(scheduleJob.getBeanClass());
                object = clazz.newInstance();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
        if (object == null) {
            log.error("任務名稱 = [" + scheduleJob.getJobName() + "]---------------未啟動成功,請檢查是否配置正確!!!");
            return;
        }
        clazz = object.getClass();
        Method method = null;
        try {
            method = clazz.getMethod(scheduleJob.getMethodName(), new Class[] {JobExecutionContext.class});
        } catch (NoSuchMethodException e) {
            log.error("任務名稱 = [" + scheduleJob.getJobName() + "]---------------未啟動成功,方法名設定錯誤!!!");
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (method != null) {
            try {
                method.invoke(object, new Object[] {context});
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        log.info("任務名稱 = [" + scheduleJob.getJobName() + "]----------啟動成功");
    }
}
package com.netease.ad.omp.quartz.job;

import com.netease.ad.omp.entity.sys.ScheduleJob;
import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


/**
 * 
 * @Description: 計劃任務執行處 無狀態
 *  Spring排程任務 (重寫 quartz 的 QuartzJobBean 類原因是在使用 quartz+spring 把 quartz 的 task 例項化進入資料庫時,會產生: serializable 的錯誤)
 */
public class QuartzJobFactory implements Job {
   public final Logger log = Logger.getLogger(this.getClass());

   @Override
   public void execute(JobExecutionContext context) throws JobExecutionException {
      ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
      JobUtils.invokMethod(scheduleJob,context);
   }
}
package com.netease.ad.omp.quartz.job;

import com.netease.ad.omp.entity.sys.ScheduleJob;
import org.apache.log4j.Logger;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;


/**
 * 
 * @Description: 若一個方法一次執行不完下次輪轉時則等待該方法執行完後才執行下一次操作
 *  Spring排程任務 (重寫 quartz 的 QuartzJobBean 類原因是在使用 quartz+spring 把 quartz 的 task 例項化進入資料庫時,會產生: serializable 的錯誤)
 */
@DisallowConcurrentExecution
public class QuartzJobFactoryDisallowConcurrentExecution implements Job {
   public final Logger log = Logger.getLogger(this.getClass());

   @Override
   public void execute(JobExecutionContext context) throws JobExecutionException {
      ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
      JobUtils.invokMethod(scheduleJob,context);

   }
}
package com.netease.ad.omp.entity.sys;

import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

/**
 * Created with IntelliJ IDEA
 * ProjectName: omp
 * Author:  bjsonghongxu
 * CreateTime : 15:48
 * Email: