分布式定时任务Spring Boot + Quartz实现
最近在工作中使用Spring Boot自带的@Scheduled执行定时任务,但是当我的应用部署在多台机器上时,发现使用@SchedulerLock来控制单节点执行定时任务有点问题,所以决定更换定时任务框架,改用Spring Boot + Quartz来做分布式定时任务控制。
废话不多说,开整!
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>
修改application.properties文件
# Quartz配置spring.quartz.job-store-type=jdbcspring.quartz.jdbc.initialize-schema=alwaysspring..quartz.scheduler.instanceName=DBaasSchedulerspring..quartz.scheduler.instanceId=AUTOspring..quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTXspring..quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegatespring..quartz.jobStore.tablePrefix=QRTZ_spring..quartz.jobStore.isClustered=truespring..quartz.jobStore.clusterCheckinInterval=10000spring..quartz.jobStore.useProperties=falsespring..quartz.threadPool.class=org.quartz.simpl.SimpleThreadPoolspring..quartz.threadPool.threadCount=10spring..quartz.threadPool.threadPriority=5spring..quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
配置参考:/quartz_doc/quartz_doc-2put2clm.html
创建定时任务需要的11张表
这次工作中的项目使用的是Postgres数据库,所以这里用的是Postgres数据库的建表脚本,如果你使用的是其他的数据库,用对应数据库的脚本即可:
-- Thanks to Patrick Lightbody for submitting this...---- In your Quartz properties file, you'll need to set -- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegateDROP 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;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 BOOL NOT NULL,IS_NONCONCURRENT BOOL NOT NULL,IS_UPDATE_DATA BOOL NOT NULL,REQUESTS_RECOVERY BOOL NOT NULL,JOB_DATA BYTEA NULL,PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP));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 NULL,PREV_FIRE_TIME BIGINT NULL,PRIORITY INTEGER NULL,TRIGGER_STATE VARCHAR(16) NOT NULL,TRIGGER_TYPE VARCHAR(8) NOT NULL,START_TIME BIGINT NOT NULL,END_TIME BIGINT NULL,CALENDAR_NAME VARCHAR(200) NULL,MISFIRE_INSTR SMALLINT NULL,JOB_DATA BYTEA 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));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 NOT NULL,REPEAT_INTERVAL BIGINT NOT NULL,TIMES_TRIGGERED BIGINT 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));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(120) 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));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 BOOL NULL,BOOL_PROP_2 BOOL 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));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 BYTEA 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));CREATE TABLE qrtz_calendars(SCHED_NAME VARCHAR(120) NOT NULL,CALENDAR_NAME VARCHAR(200) NOT NULL,CALENDAR BYTEA NOT NULL,PRIMARY KEY (SCHED_NAME,CALENDAR_NAME));CREATE TABLE qrtz_paused_trigger_grps(SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP));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 NOT NULL,SCHED_TIME BIGINT NOT NULL,PRIORITY INTEGER NOT NULL,STATE VARCHAR(16) NOT NULL,JOB_NAME VARCHAR(200) NULL,JOB_GROUP VARCHAR(200) NULL,IS_NONCONCURRENT BOOL NULL,REQUESTS_RECOVERY BOOL NULL,PRIMARY KEY (SCHED_NAME,ENTRY_ID));CREATE TABLE qrtz_scheduler_state (SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_NAME VARCHAR(200) NOT NULL,LAST_CHECKIN_TIME BIGINT NOT NULL,CHECKIN_INTERVAL BIGINT NOT NULL,PRIMARY KEY (SCHED_NAME,INSTANCE_NAME));CREATE TABLE qrtz_locks(SCHED_NAME VARCHAR(120) NOT NULL,LOCK_NAME VARCHAR(40) NOT NULL, 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);commit;
定时任务入口类
import org.quartz.DisallowConcurrentExecution;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.springframework.scheduling.quartz.QuartzJobBean;/*** @author age* @description 定时任务逻辑类 @DisallowConcurrentExecution 这个注解很重要,表示只能由一个节点执行定时任务* @date /11/26 14:06*/@DisallowConcurrentExecutionpublic class TestJob extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {// 这里面写业务逻辑System.out.println("定时任务开始啦");}}
定时任务的Job类可以继承QuartzJobBean类,也可以实现接口Job,都是一样的效果。
Quartz配置类
import com.foresealife.dbaas.task.test.TestJob;import org.quartz.*;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/*** @author age* @description Quartz配置类* @date /11/25 9:49*/@Configurationpublic class QuartzConfiguration {/*** 使用 JobDetail 包装 TestJob* @return JobDetail*/@Beanpublic JobDetail testJobDetail() {return JobBuilder.newJob(TestJob.class).withIdentity("TestJob").storeDurably().build();}/*** 把 JobDetail 注册到 Cron 表达式的 trigger 上去* @return Trigger*/@Beanpublic Trigger testJobTrigger() {CronScheduleBuilder cronScheduleBuilder =CronScheduleBuilder.cronSchedule("0/10 * * * * ?").withMisfireHandlingInstructionDoNothing();return TriggerBuilder.newTrigger().forJob(testJobDetail()).withIdentity("TestJobTrigger").withSchedule(cronScheduleBuilder).build();}}
.withMisfireHandlingInstructionDoNothing()这个属性表示当错过当前这一次任务,不做任何操作,等待下一次任务的开始,在这里主要是为了防止应用一启动,就立即执行一次定时任务。
启动Spring Boot
定时任务开始啦定时任务开始啦定时任务开始啦定时任务开始啦定时任务开始啦
可以看到我们的定时任务已经执行。
看一下我们的Quartz相关的表里面是否有数据写入:
select * from QRTZ_SCHEDULER_STATE;
select * from QRTZ_LOCKS;
select * from QRTZ_CRON_TRIGGERS;
select * from QRTZ_TRIGGERS;
select * from QRTZ_JOB_DETAILS;
(如果没有使用特殊的功能,正常只有这五张表有数据)
可以看到我们的表里面已经有数据写进来了,Quartz就是根据这几张表来控制当你有多个节点都部署了应用,只有一个节点会进行定时任务。
感兴趣的小伙伴可以自己试一下启动两个节点,看是否只有一个节点会执行,我这里已经试过了,是OK的,但是当时测试没有进行记录,所以这里就不贴测试结果了。
麻烦!懒!请谅解!