Quartz框架
- Quartz三大组件JobDetail,Trigger,Scheduler
- JobDetail和Trigger可以使用jobDataMap()给job传值
SpringBoot整合Quartz
-
创建数据库Quartz关联表(必须)
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; # 1、存储每一个已配置的 jobDetail 的详细信息 CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); # 2、 存储已配置的 Trigger 的信息 CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); # 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); # 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); # 11、用来存储存储CalendarIntervalTrigger和DailyTimeIntervalTrigger CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); # 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); # 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); # 7、 存储已暂停的 Trigger 组的信息 CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); # 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); # 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); # 10、 存储程序的悲观锁的信息(假如使用了悲观锁) CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); commit;
-
引入Maven依赖
<!--quartz依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
-
新建并编写quartz.properties配置文件
#调度标识名,集群中每个实例都必须保持一致 org.quartz.scheduler.instanceName=ClusterQuartz #ID自动获取,集群节点每一个必须不同 org.quartz.scheduler.instanceId=AUTO #是否加入集群,通知Scheduler实例要它参与到一个集群当中 org.quartz.jobStore.isClustered=true #集群调度实例失效的检查时间间隔ms org.quartz.jobStore.clusterCheckinInterval=5000 #事务是否托管 org.quartz.jobStore.txIsolationLevelReadCommitted=true #数据保存方式为数据库持久化 org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX #数据库代理类 org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate #Quartz表前缀,默认QRTZ_ org.quartz.jobStore.tablePrefix=QRTZ_ #jobDataMap是否都为String类型,默认false org.quartz.jobStore.useProperties=false #线程池的实现类 org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool #指定线程数 org.quartz.threadPool.threadCount=3 #设置线程优先级 org.quartz.threadPool.threadPriority=5
-
编写Job任务类继承QuartzJobBean
(Job任务类中不用使用@Autowired进行自动注入,Job 对象实例化的过程是通过 Quartz 内部自己完成的,但是我们通过 Spring 进行注入的 Bean 却是由 Spring 容器管理的,Quartz 内部无法感知到 Spring 容器管理的 Bean,所以没有办法在创建 Job 的时候就给装配进去。需要手动进行注入NcrReportUtil bean = SpringUtil.getBean(NcrReportUtil.class);)@PersistJobDataAfterExecution @DisallowConcurrentExecution public class QuartzJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { try { //执行任务 Thread.sleep(1000); System.out.println("taskName="+jobExecutionContext.getJobDetail().getKey().getName()+" "+LocalDateTime.now()); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
配置调度器Scheduler只需要有一个实例即可
@Configuration public class SchedulerConfig { @Autowired private DataSource dataSource; @Bean public Scheduler scheduler() { return this.getSchedulerFactoryBean().getScheduler(); } @Bean public SchedulerFactoryBean getSchedulerFactoryBean() { //配置SchedulerFactoryBean SchedulerFactoryBean sc = new SchedulerFactoryBean(); sc.setApplicationContextSchedulerContextKey("application"); sc.setSchedulerName("cluster_scheduler"); sc.setDataSource(dataSource); sc.setQuartzProperties(getProperties()); sc.setTaskExecutor(schedulerThreadPool()); return sc; } public Properties getProperties() { try { PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean(); //设置自定义配置文件的位置 propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties")); //读取quartz配置文件 propertiesFactoryBean.afterPropertiesSet(); return propertiesFactoryBean.getObject(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } } @Bean public Executor schedulerThreadPool() { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(), 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)); return threadPoolExecutor; } }
-
编写Spring容器启动监听器
/** * 监听springboot容器启动时 */ @Component public class TaskListener implements ApplicationListener { @Autowired private Scheduler scheduler; @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { try { //集群测试任务1 TriggerKey triggerKey1 = TriggerKey.triggerKey("trigger1", "group1"); Trigger trigger1 = scheduler.getTrigger(triggerKey1); if (trigger1==null){ trigger1= TriggerBuilder.newTrigger() .withIdentity(triggerKey1) .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) .startNow() .build(); JobDetail jobDetail1 = JobBuilder.newJob(QuartzJob.class) .withIdentity("job1", "group1") .build(); scheduler.scheduleJob(jobDetail1,trigger1); } //集群测试任务2 TriggerKey triggerKey2 = TriggerKey.triggerKey("trigger2", "group2"); Trigger trigger2 = scheduler.getTrigger(triggerKey2); if (trigger2==null){ trigger2= TriggerBuilder.newTrigger() .withIdentity(triggerKey2) .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")) .startNow() .build(); JobDetail jobDetail2 = JobBuilder.newJob(QuartzJob.class) .withIdentity("job2", "group2") .build(); scheduler.scheduleJob(jobDetail2,trigger2); } //多个jobDetail时多个scheduleJob()方法,但start()方法只用执行一次。 scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } }
一般job类都要加上这两个注解
@DisallowConcurrentExecution: Quartz默认是多线程并发执行job,每次都会创建新的job和JobDetail实例,上一次任务如果没执行完,后面的也不会等它。如果想只能有一个在执行可以在job类上使用@DisallowConcurentExecution
@PersistJobDataAfterExecution: job类上加@PersistJobDataAfterExecution:保持JobDetail中的jobDataMap()持久化,既只有一个实例。但对Trigger的无效。
Quartz集群
集群节点互相不通讯,要通过数据库表来实现集群的通讯。
Quartz集群原理:一个jobDetail和Trigger会自动分配给一个集群节点,而不是一个任务上一次执行在一个节点下一次执行又跑到另一个节点了。
(一个jobDetail就会在一个集群节点上执行,如果有多个jobDetail且有多个集群节点才会自动分配,但如果有多个jobDetail但只有一个集群节点,活就只能那一个节点全揽了)
同一个集群中,由于通过数据库实现高可用,就算每个集群节点定时任务的代码不一样也不会影响,Quartz还是会平均分配任务。
注意事项
定时任务的 Job 对象实例化的过程是通过 Quartz 内部自己完成的,但是我们通过 Spring 进行注入的 Bean 却是由 Spring 容器管理的,Quartz 内部无法感知到 Spring 容器管理的 Bean,所以没有办法在创建 Job 的时候就用@Autowired给装配进去。解决办法是需要手动注入Bean: NcrReportUtil bean = SpringUtil.getBean(NcrReportUtil.class);