由于quartz单节点无法满足业务的需求,后面我们在单节点的基础上进行了集群部署。
由以前的定时任务信息放在jobs.xml配置文件中,转而放到数据库中。
1、新建数据库表
CREATE TABLE qrtz_job_details ( SCHED_NAME VARCHAR2(120) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, JOB_CLASS_NAME VARCHAR2(250) NOT NULL, IS_DURABLE VARCHAR2(1) NOT NULL, IS_NONCONCURRENT VARCHAR2(1) NOT NULL, IS_UPDATE_DATA VARCHAR2(1) NOT NULL, REQUESTS_RECOVERY VARCHAR2(1) NOT NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, JOB_NAME VARCHAR2(200) NOT NULL, JOB_GROUP VARCHAR2(200) NOT NULL, DESCRIPTION VARCHAR2(250) NULL, NEXT_FIRE_TIME NUMBER(13) NULL, PREV_FIRE_TIME NUMBER(13) NULL, PRIORITY NUMBER(13) NULL, TRIGGER_STATE VARCHAR2(16) NOT NULL, TRIGGER_TYPE VARCHAR2(8) NOT NULL, START_TIME NUMBER(13) NOT NULL, END_TIME NUMBER(13) NULL, CALENDAR_NAME VARCHAR2(200) NULL, MISFIRE_INSTR NUMBER(2) NULL, JOB_DATA BLOB NULL, CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE qrtz_simple_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, REPEAT_COUNT NUMBER(7) NOT NULL, REPEAT_INTERVAL NUMBER(12) NOT NULL, TIMES_TRIGGERED NUMBER(10) NOT NULL, CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_cron_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CRON_EXPRESSION VARCHAR2(120) NOT NULL, TIME_ZONE_ID VARCHAR2(80), CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_simprop_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, STR_PROP_1 VARCHAR2(512) NULL, STR_PROP_2 VARCHAR2(512) NULL, STR_PROP_3 VARCHAR2(512) NULL, INT_PROP_1 NUMBER(10) NULL, INT_PROP_2 NUMBER(10) NULL, LONG_PROP_1 NUMBER(13) NULL, LONG_PROP_2 NUMBER(13) NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR2(1) NULL, BOOL_PROP_2 VARCHAR2(1) NULL, CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_blob_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, BLOB_DATA BLOB NULL, CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_calendars ( SCHED_NAME VARCHAR2(120) NOT NULL, CALENDAR_NAME VARCHAR2(200) NOT NULL, CALENDAR BLOB NOT NULL, CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE qrtz_paused_trigger_grps ( SCHED_NAME VARCHAR2(120) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE qrtz_fired_triggers ( SCHED_NAME VARCHAR2(120) NOT NULL, ENTRY_ID VARCHAR2(95) NOT NULL, TRIGGER_NAME VARCHAR2(200) NOT NULL, TRIGGER_GROUP VARCHAR2(200) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, FIRED_TIME NUMBER(13) NOT NULL, PRIORITY NUMBER(13) NOT NULL, STATE VARCHAR2(16) NOT NULL, JOB_NAME VARCHAR2(200) NULL, JOB_GROUP VARCHAR2(200) NULL, IS_NONCONCURRENT VARCHAR2(1) NULL, REQUESTS_RECOVERY VARCHAR2(1) NULL, CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE qrtz_scheduler_state ( SCHED_NAME VARCHAR2(120) NOT NULL, INSTANCE_NAME VARCHAR2(200) NOT NULL, LAST_CHECKIN_TIME NUMBER(13) NOT NULL, CHECKIN_INTERVAL NUMBER(13) NOT NULL, CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE qrtz_locks ( SCHED_NAME VARCHAR2(120) NOT NULL, LOCK_NAME VARCHAR2(40) NOT NULL, CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY); create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME); create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP); create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE); create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME); create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP); create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP); create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP);
2、 quartz.properties文件修改
以前该配置不是放在classpath下,修改后需要将改配置放在classpath下
#============================================================================ # Configure Main Scheduler Properties #============================================================================ org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.scheduler.instanceId = AUTO #============================================================================ # Configure ThreadPool #============================================================================ org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5 #============================================================================ # Configure JobStore #============================================================================ org.quartz.jobStore.misfireThreshold = 60000 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate ##org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate #org.quartz.jobStore.useProperties = false org.quartz.jobStore.dataSource = myDS org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.jobStore.isClustered = false #============================================================================ # Configure Datasources #============================================================================ #org.quartz.dataSource.myDS.driver = org.postgresql.Driver #org.quartz.dataSource.myDS.URL = jdbc:postgresql:dev #org.quartz.dataSource.myDS.user = jhouse #org.quartz.dataSource.myDS.password = #org.quartz.dataSource.myDS.maxConnections = 5 #org.quartz.dataSource.myDS.validationQuery = select lock_name from qrtz_locks where lock_name = 'TRIGGER_ACCESS'; #============================================================================ # Configure Plugins #============================================================================ # Uncomment the following to get logging of job execution events... #org.quartz.plugin.jobHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin # Uncomment the following to get logging of trigger firing events... #org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin #org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin #org.quartz.plugin.jobInitializer.fileName = jobs.xml #org.quartz.plugin.jobInitializer.overWriteExistingJobs = true #org.quartz.plugin.jobInitializer.failOnFileNotFound = true #org.quartz.plugin.jobInitializer.useContextClassLoader = true #org.quartz.plugin.jobInitializer.validating = false #org.quartz.plugin.jobInitializer.validatingSchema = true #org.quartz.plugin.jobInitializer.scanInterval = 1800 #============================================================================ # Configure Listeners #============================================================================ #org.quartz.jobListener.dummy.class = org.quartz.examples.DumbJobListener
3、与spring集成
修改applicationContext.xml,添加如下配置
4、添加监听器
由于需要对触发器的执行情况进行监控所以在servlet初始化时添加了监听器。
private void initQuartz(ServletConfig cfg) { System.out.println("Quartz 开始初始化..."); try { //启动调度 scheduler = (Scheduler)BeanLocator.getBeanInstance("bdcscheduler"); TriggerListener myListener=new MonitorTriggerListener(); scheduler.addGlobalTriggerListener(myListener); System.out.println("名称为 " + scheduler.getSchedulerName() + "的任务调度启动正常"); } catch (Exception e) { System.out.println("Quartz 初始化异常: " + e.toString()); logger.error(e); } }
5、将原有jobs.xml中的job配置信息初始化到数据库
首先解析jobs.xml,然后初始化成jobdetail和crontrigger,依次执行scheduler.scheduleJob
public static ListfilterJobXmlFile() throws Exception { String configFileName = "jobs.xml"; String jobXmlFileFullName = ConfigContext.getFullConfigPath("quartz",configFileName); // String jobXmlFileFullName = ConfigurationHelper.getFullFileName(configFile); List simsJobList = new ArrayList (); logger.debug("构造文档对象(对xml 文件进行解析)"); // 构造文档对象(对xml 文件进行解析) Document doc = FeatureFilterUtils.parseXMLFile(jobXmlFileFullName); Element root = doc.getRootElement(); // 判断是否存在命名空间,如果有命名空间就应该根据命名空间取得子节点 Namespace namespace = root.getNamespace(); root.removeNamespaceDeclaration(namespace); // root.setNamespace(null); // 根据feature配置进行过滤取得关闭的feature列表(包含停止JOB运行的所有配置项) // removeFeatureClose(root, namespace); @SuppressWarnings("unchecked") List list = root.getChildren("job",namespace); for (Element element : list) { SimsJob simsJob = new SimsJob(); @SuppressWarnings("unchecked") List eleList = element.getChildren("job-detail",namespace); Map paraMap = new HashMap (); for (Element ele : eleList) { simsJob.setName(ele.getChild("name",namespace).getValue()); simsJob.setGroup(ele.getChild("group",namespace).getValue()); simsJob.setJobClass(ele.getChild("job-class",namespace).getValue()); simsJob.setVolatility(ele.getChild("volatility",namespace).getValue()); simsJob.setDurability(ele.getChild("durability",namespace).getValue()); simsJob.setRecover(ele.getChild("recover",namespace).getValue()); Element ele1 =ele.getChild("job-data-map",namespace); if(ele1==null){ continue; } List ele2List = ele1.getChildren("entry",namespace); for (Element ele3 : ele2List) { String key = ele3.getChild("key",namespace).getValue(); String value = ele3.getChild("value",namespace).getValue(); paraMap.put(key, value); } } simsJob.setParaMap(paraMap); simsJob.setServiceName(paraMap.get("serviceName")); String jobDesc = paraMap.get("desc"); if(StringUtils.isEmpty(jobDesc)){ jobDesc="没设置job描述,以名称代替:"+simsJob.getName(); } simsJob.setDesc(jobDesc); Element trigger = element.getChild("trigger",namespace); @SuppressWarnings("unchecked") List triggerList = trigger.getChildren(); for (Element ele : triggerList) { simsJob.setTriggerName(ele.getChild("name",namespace).getValue()); simsJob.setTriggerGroup(ele.getChild("group",namespace).getValue()); simsJob.setTriggerCronExpression(ele .getChild("cron-expression",namespace).getValue()); } simsJobList.add(simsJob); } return simsJobList; }
/** * 初始化xml配置到数据库 * initDbQuartz * @param mapping * @param form * @param request * @return */ public voidinitDbQuartz(ActionMapping mapping, ActionForm form, HttpServletRequest request) { ListjobList = null; try { jobList = SimsJobXmlUtil.filterJobXmlFile(); for (SimsJob simsJob : jobList) { Class ownerClass = Class.forName(simsJob.getJobClass()); Constructor constructor = ownerClass.getConstructor(); Job job = (Job) constructor.newInstance(); // 执行一下JOB Scheduler scheduler = (Scheduler) BeanLocator.getBeanInstance("bdcscheduler"); TriggerListener myListener = new MonitorTriggerListener(); scheduler.addGlobalTriggerListener(myListener); JobDetail jobDetail = new JobDetail(simsJob.getName(), simsJob.getGroup(), job.getClass(), BooleanUtils.toBoolean(simsJob .getVolatility()), BooleanUtils.toBoolean(simsJob.getDurability()), BooleanUtils.toBoolean(simsJob.getRecover())); jobDetail.setDescription(simsJob.getDesc()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAll(simsJob.getParaMap()); jobDetail.setJobDataMap(jobDataMap); Trigger trigger = new CronTrigger(simsJob.getTriggerName(), simsJob.getTriggerGroup(), simsJob.getName(), simsJob.getGroup(), simsJob.getTriggerCronExpression()); trigger.setDescription(simsJob.getDesc()); scheduler.scheduleJob(jobDetail, trigger); } } catch (Exception e) { logger.error("列表出Job对象 listSimsJobs 出错", e); e.printStackTrace(); } }
6、手动执行
所谓手动执行就是new一个simpletrigger来执行一次job,如下:
public static void runJob(String jobName) { logger.debug("执行选择的Job对象 doRunSimsJob runName:" + jobName); try { // 执行一下JOB Scheduler scheduler = (Scheduler) BeanLocator.getBeanInstance("bdcscheduler"); SimsJob simsJob = null; boolean existFlag = false; if (jobName != null && !"".equals(jobName)) { // 通过调度器对象scheduler获取所有触发器组名称数组TriggerGroupNames String[] triggerGroups = scheduler.getTriggerGroupNames(); for (int i = 0; i < triggerGroups.length; i++) {// 遍历每个触发器组名数组,得到每组所有触发器 String groupName = triggerGroups[i]; // 根据触发器组名获得该组所有触发器名称数组 String[] triggerNames = scheduler.getTriggerNames(groupName); for (int j = 0; j < triggerNames.length; j++) {// 遍历每个触发器名数组,得到当前组所有触发器 if (existFlag) { break; } simsJob = new SimsJob(); String triggerName = triggerNames[j]; // 根据触发器名称获得触发器对象 CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerName, groupName); if (jobName.equals(trigger.getJobName())) { // 对每个触发器对象TriggerDTO设属性值 simsJob.setDesc(trigger.getDescription()); simsJob.setGroup(trigger.getJobGroup()); simsJob.setName(trigger.getJobName()); JobDetail jobdetail=scheduler.getJobDetail(trigger.getJobName(), trigger.getJobGroup()); simsJob.setJobClass(jobdetail.getJobClass().getName()); simsJob.setParaMap(jobdetail.getJobDataMap()); simsJob.setTriggerGroup(trigger.getGroup()); simsJob.setTriggerName(trigger.getName()); simsJob.setTriggerCronExpression(trigger.getCronExpression()); existFlag = true; } } if (existFlag) { break; } } } if (existFlag) { Class clazz=Class.forName(simsJob.getJobClass()); // 实例化控制的Job Constructor constructor = clazz.getConstructor(); Job job = (Job) constructor.newInstance(); TriggerListener myListener = new MonitorTriggerListener(); scheduler.addGlobalTriggerListener(myListener); JobDetail jobDetail = new JobDetail(simsJob.getName(), Scheduler.DEFAULT_GROUP, job.getClass()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAll(simsJob.getParaMap()); jobDetail.setJobDataMap(jobDataMap); Trigger trigger = new SimpleTrigger(simsJob.getTriggerName(), Scheduler.DEFAULT_GROUP, new Date(), null, 0, 0L); scheduler.scheduleJob(jobDetail, trigger); scheduler.start(); logger.error("执行选择的Job对象:" + simsJob.getName() + "已经开始执行,稍后请检查数据!"); } else { logger.error("执行选择的Job对象出错:该Job不存在于Job配置文件中,请确认Job 名称是否正确!"); } } catch (Exception e) { logger.error("doRunSimsJob 执行选择的Job对象出错", e); e.printStackTrace(); } }
7、升级quartz
由于我们用的spring版本是3.2.16,而spring3.2.16中org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger(public class CronTriggerBeanextends CronTrigger),而在quartz2.3.3中org.quartz.CronTrigger是个接口(publicabstract interface CronTrigger extends Trigger),而我们系统中有部分的类库中用到了CronTriggerBean,继承了CronTriggerBean,导致升级到最新版本quartz2.3.3时报错,所以只升级到了quartz1.8.6。
这里特别要注意的是quartz升级到1.8.6后如果任然需要使用xml的话,会和1.6版本有很大区别。如下
1.6版本如下:
holidayCalendar HolidayCalendar weeklyCalendar WeeklyCalendar annualCalendar AnnualCalendar DataDictionaryCache taskGroup com.aspire.sims.uspc.platform.job.ScheduleServiceProcessor false false false serviceName dataDictionaryCache desc uspc数据字典缓存加载任务 DataDictionaryCacheTrigger triggerGroup DataDictionaryCache taskGroup 0 0 23 * * ?
而下面的则是1.8.6
approveNotifyJob approveNotifyJob_group 集成sims审批催办提醒job com.aspire.bdc.common.job.ApproveNotifyJob false false false remind_spserv spServApproveNotifyService cronTrigger_approveNotifyJob triggerGroup 集成sims审批催办提醒定时器 approveNotifyJob approveNotifyJob_group 0 30 15 ? * MON,WED,FRI