關於quartz框架做叢集定時任務排程的總結(註解配置的方式)
阿新 • • 發佈:2019-01-04
接上文,quartz採用2.2.1版本,11張資料庫的表格,
1,quartz.properties 配置檔案不變(跟上文一樣):
#============================================================== #Configure Main Scheduler Properties #============================================================== org.quartz.scheduler.instanceName = mapScheduler org.quartz.scheduler.instanceId = AUTO #org.quartz.scheduler.instanceIdGenerator.class #============================================================== #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 = true #============================================================== #Configure DataSource #============================================================== # org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver # org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/sdn?useUnicode=true&characterEncoding=UTF-8 # org.quartz.dataSource.myDS.user = root # 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
2,application-quartz.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" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd "> <!-- 新加的配置 --> <bean id="mapScheduler" 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="30" /> <!-- 設定自動啟動 --> <property name="autoStartup" value="true" /> <property name="jobFactory"> <bean class="com.ky.sdn.fk.server.quartz.SpringQuartzJobFactory
com.ky.sdn.fk.server.quartz.SpringQuartzJobFactory"></bean> </property> <property name="dataSource" ref="dataSource"/> <!-- 要記得要指定配置檔案的位置 --> <property name="configLocation" value="classpath:properties/quartz.properties" /> </bean> </beans>"></bean> </property> <property name="dataSource" ref="dataSource"/> <!-- 要記得要指定配置檔案的位置 --> <property name="configLocation" value="classpath:properties/quartz.properties" /> </bean> </beans>
3,編寫一個任務註冊排程監聽的類,實現ApplicationListener(由springring容器管理):
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.TriggerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
@Component
public class QuartJobSchedulingListener implements ApplicationListener<ContextRefreshedEvent>
{
Logger logger = LoggerFactory.getLogger(QuartJobSchedulingListener.class);
@Autowired
private Scheduler scheduler;
public void onApplicationEvent(ContextRefreshedEvent event)
{
try
{
ApplicationContext applicationContext = event.getApplicationContext();
this.loadCronTriggers(applicationContext, scheduler);
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
private void loadCronTriggers(ApplicationContext applicationContext, Scheduler scheduler)
{
Map<String, Object> quartzJobBeans = applicationContext.getBeansWithAnnotation(QuartzJob.class);
Set<String> beanNames = quartzJobBeans.keySet();
List<CronTrigger> cronTriggerBeans = new ArrayList<CronTrigger>();
for (String beanName : beanNames)
{
Object object = quartzJobBeans.get(beanName);
try
{
if (Job.class.isAssignableFrom(object.getClass()))
{
QuartzJob quartzJobAnnotation = AnnotationUtils.findAnnotation(object.getClass(), QuartzJob.class);
JobKey jobKey = new JobKey(quartzJobAnnotation.name(), quartzJobAnnotation.group());
JobDetail job = JobBuilder
.newJob((Class<? extends Job>) object.getClass())
.withIdentity(jobKey)
.build();
CronTrigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity(quartzJobAnnotation.name() + "_trigger", quartzJobAnnotation.group())
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJobAnnotation.cronExp()))
.build();
scheduler.scheduleJob(job, cronTrigger);
}
else
{
String errorMsg = object.getClass() + " doesn't implemented " + Job.class.getName();
logger.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
}
}
}
}
5,編寫一個SpringBeanJobFactory 的繼承類
import org.quartz.Job;
import org.quartz.spi.TriggerFiredBundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.stereotype.Component;
public class SpringQuartzJobFactory extends SpringBeanJobFactory
{
Logger logger = LoggerFactory.getLogger(SpringQuartzJobFactory.class);
@Autowired
private ApplicationContext ctx;
@Override
@SuppressWarnings("unchecked")
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception
{
Job job = null;
try
{
job = ctx.getBean(bundle.getJobDetail().getJobClass());
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(job);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(bundle.getJobDetail().getJobDataMap());
pvs.addPropertyValues(bundle.getTrigger().getJobDataMap());
bw.setPropertyValues(pvs, true);
}
catch (Exception e)
{
logger.error(e.getMessage(), e);
throw new Exception(e);
}
return job;
}
}
6,任務定時策略的註解需要自定義去編寫
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
//@Scope("prototype")
public @interface QuartzJob
{
String name();
String group() default "DEFAULT_GROUP";
String cronExp();
}
7,編寫任務類,用註解去實現定時
import java.util.Date;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
@QuartzJob(name = "HelloJob", cronExp = "*/10 * * * * ?")
public class HelloJob extends QuartzJobBean
{
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException
{
System.out.println("Hello Job is running @ " + new Date());
System.out.println(this.hashCode());
}
}
name = "HelloJob", cronExp = "*/10 * * * * ?")
public class HelloJob extends QuartzJobBean
{
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException
{
System.out.println("Hello Job is running @ " + new Date());
System.out.println(this.hashCode());
}
}
總結:這種方法的註解是隻能一個類寫同一種定時策略的n個方法,不能一個類寫不同定時策略的n個方法,就算把自定義註解改為方法的註解型別,但是QuartJobSchedulingListener這個類引用了自定義註解類,能力有限,不會改原始碼了,所以使用具有一點點侷限性,一個類只能實現同種策略的方法。
注意:容器首次正常啟動後,是不會報異常資訊的,再次啟動時會報異常,如下:
org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'DEFAULT_GROUP.HelloJob', because one already exists with this identification.
大概意思是表中XXX已經有了資料,不能繼續儲存進去:
大概是說資料庫已經存在了這個HelloJob,也就是容器關閉時沒有刪除表中資訊,配置這個怎麼刪除我也沒搞懂,但是不影響使用
還有一張表需要注意:
這裡面有一些cron的表示式,因為重啟容器表的資料不會清空,所以改程式碼的cron表示式的時候記得把job的name也改成表中沒有的,這個大概跟triggerName有關係的。(上一篇純配置實現的時候是不會出現報異常的問題的,大概是已經清空了表,這點待確認一下???)