继上篇文章记录了XXL-JOB的使用后,但在服务器多节点部署的情况下始终无法生效,可能是因为docker环境导致网络没有打通,原因不明~于是放弃,改用Quartz。
以下记录开发过程。
1. 数据库准备
前往官网下载压缩包(http://www.quartz-scheduler.org/downloads/)
我下载的是2.2.3
版,因为2.3.0
版本的压缩包里面没有相关sql文件。
我不使用项目的库,所以新建一个叫quartz_job
的库,然后执行一下sql。
CREATE SCHEMA `quartz_job` DEFAULT CHARACTER SET utf8mb4;
USE `quartz_job`;
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
#
# By: Ron Cordell - roncordell
# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.
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;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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),
INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
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))
ENGINE=InnoDB;
CREATE TABLE QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME,LOCK_NAME))
ENGINE=InnoDB;
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;
2. Maven依赖
因为使用了spring boot framework,所以加入下面依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
3. 配置quartz
修改application.yml
文件
spring:
quartz:
properties:
org:
quartz:
scheduler:
instanceName: MyScheduler
instanceId: AUTO
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
tablePrefix: QRTZ_
isClustered: true
clusterCheckinInterval: 10000
dataSource: quartz_job
#以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,因此可以作为名称
#- 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。
#从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题。
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
dataSource:
quartz_job:
URL: jdbc:mysql://127.0.0.1:3306/quartz_job?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
user: root
password: 123456
driver: com.mysql.cj.jdbc.Driver
provider: hikaricp
4. 添加执行器
import org.apache.logging.log4j.LogManager
import org.quartz.Job
import org.quartz.JobExecutionContext
/**
* @Author Anson
* @Date 2022/1/6 17:13
* @Version 1.0
*/
class MyQuartzJob : Job {
private val logger = LogManager.getLogger()
override fun execute(p0: JobExecutionContext?) {
logger.info("test job")
}
}
5. 实现Quartz接口,方便创建与执行任务
DTO
data class QuartzConfigDTO(
/**
* 任务名称
*/
val jobName: String,
/**
* 任务所属组
*/
val groupName: String,
/**
* 任务执行类
*/
val jobClass: String,
/**
* 任务调度时间表达式
*/
val cronExpression: String,
/**
* 附加参数
*/
val param: Map<String, Any>
) : Serializable
Interface
/**
* @Author Anson
* @Date 2022/1/6 17:22
* @Version 1.0
*/
interface JobService {
/**
* 添加任务可以传参数
* @param clazzName
* @param jobName
* @param groupName
* @param cronExp
* @param param
*/
fun addJob(clazzName: String, jobName: String, groupName: String, cronExp: String, param: Map<String, Any>)
/**
* 暂停任务
* @param jobName
* @param groupName
*/
fun pauseJob(jobName: String, groupName: String)
/**
* 恢复任务
* @param jobName
* @param groupName
*/
fun resumeJob(jobName: String, groupName: String)
/**
* 立即运行一次定时任务
* @param jobName
* @param groupName
*/
fun runOnce(jobName: String, groupName: String)
/**
* 更新任务
* @param jobName
* @param groupName
* @param cronExp
* @param param
*/
fun updateJob(jobName: String, groupName: String, cronExp: String?, param: Map<String, Any>?)
/**
* 删除任务
* @param jobName
* @param groupName
*/
fun deleteJob(jobName: String, groupName: String)
/**
* 启动所有任务
*/
fun startAllJobs()
/**
* 暂停所有任务
*/
fun pauseAllJobs()
/**
* 恢复所有任务
*/
fun resumeAllJobs()
/**
* 关闭所有任务
*/
fun shutdownAllJobs()
}
Service
/**
* @Author Anson
* @Date 2022/1/6 17:23
* @Version 1.0
*/
@Component
class JobServiceImpl(
private val scheduler: Scheduler
) : JobService {
private val logger = LogManager.getLogger()
override fun addJob(
clazzName: String,
jobName: String,
groupName: String,
cronExp: String,
param: Map<String, Any>
) {
try {
//构建job信息
val jobClass: Class<out Job> = Class.forName(clazzName) as Class<out Job>
val jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, groupName).build()
//表达式调度构建器(即任务执行的时间)
val scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp)
//按新的cronExpression表达式构建一个新的trigger
val trigger: CronTrigger =
TriggerBuilder.newTrigger().withIdentity(jobName, groupName).withSchedule(scheduleBuilder).build()
//获得JobDataMap,写入数据
trigger.jobDataMap.putAll(param)
scheduler.scheduleJob(jobDetail, trigger)
} catch (e: Exception) {
logger.error("创建任务失败", e)
}
}
override fun pauseJob(jobName: String, groupName: String) {
try {
scheduler.pauseJob(JobKey.jobKey(jobName, groupName))
} catch (e: SchedulerException) {
logger.error("暂停任务失败", e)
}
}
override fun resumeJob(jobName: String, groupName: String) {
try {
scheduler.resumeJob(JobKey.jobKey(jobName, groupName))
} catch (e: SchedulerException) {
logger.error("恢复任务失败", e)
}
}
override fun runOnce(jobName: String, groupName: String) {
try {
scheduler.triggerJob(JobKey.jobKey(jobName, groupName))
} catch (e: SchedulerException) {
logger.error("立即运行一次定时任务失败", e)
}
}
override fun updateJob(jobName: String, groupName: String, cronExp: String?, param: Map<String, Any>?) {
try {
val triggerKey = TriggerKey.triggerKey(jobName, groupName)
var trigger: CronTrigger = scheduler.getTrigger(triggerKey) as CronTrigger
if (cronExp != null) {
// 表达式调度构建器
val scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExp)
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.triggerBuilder.withIdentity(triggerKey).withSchedule(scheduleBuilder).build()
}
//修改map
if (param != null) {
trigger.jobDataMap.putAll(param)
}
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger)
} catch (e: Exception) {
logger.error("更新任务失败", e)
}
}
override fun deleteJob(jobName: String, groupName: String) {
try {
//暂停、移除、删除
scheduler.pauseTrigger(TriggerKey.triggerKey(jobName, groupName))
scheduler.unscheduleJob(TriggerKey.triggerKey(jobName, groupName))
scheduler.deleteJob(JobKey.jobKey(jobName, groupName))
} catch (e: Exception) {
logger.error("删除任务失败", e)
}
}
override fun startAllJobs() {
try {
scheduler.start()
} catch (e: Exception) {
logger.error("开启所有的任务失败", e)
}
}
override fun pauseAllJobs() {
try {
scheduler.pauseAll()
} catch (e: Exception) {
logger.error("暂停所有任务失败", e)
}
}
override fun resumeAllJobs() {
try {
scheduler.resumeAll()
} catch (e: Exception) {
logger.error("恢复所有任务失败", e)
}
}
override fun shutdownAllJobs() {
try {
if (!scheduler.isShutdown) {
// 需谨慎操作关闭scheduler容器
// scheduler生命周期结束,无法再 start() 启动scheduler
scheduler.shutdown(true)
}
} catch (e: Exception) {
logger.error("关闭所有的任务失败", e)
}
}
}
Controller
/**
* @Author Anson
* @Date 2022/1/6 17:33
* @Version 1.0
*/
@RestController
@RequestMapping(value = ["/v1/quartz-jobs"])
class JobController(
private val jobService: JobService
) {
/**
* 添加新任务
* @param configDTO
* @return
*/
@RequestMapping
fun addJob(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.addJob(
configDTO.jobClass,
configDTO.jobName,
configDTO.groupName,
configDTO.cronExpression,
configDTO.param
)
return HttpStatus.OK
}
/**
* 暂停任务
* @param configDTO
* @return
*/
@RequestMapping("/_pause")
fun pauseJob(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.pauseJob(configDTO.jobName, configDTO.groupName)
return HttpStatus.OK
}
/**
* 恢复任务
* @param configDTO
* @return
*/
@RequestMapping("/_resume")
fun resumeJob(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.resumeJob(configDTO.jobName, configDTO.groupName)
return HttpStatus.OK
}
/**
* 立即运行一次定时任务
* @param configDTO
* @return
*/
@RequestMapping("/_runOnce")
fun runOnce(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.runOnce(configDTO.jobName, configDTO.groupName)
return HttpStatus.OK
}
/**
* 更新任务
* @param configDTO
* @return
*/
@PutMapping
fun updateJob(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.updateJob(configDTO.jobName, configDTO.groupName, configDTO.cronExpression, configDTO.param)
return HttpStatus.OK
}
/**
* 删除任务
* @param configDTO
* @return
*/
@DeleteMapping
fun deleteJob(@RequestBody configDTO: QuartzConfigDTO): HttpStatus {
jobService.deleteJob(configDTO.jobName, configDTO.groupName)
return HttpStatus.OK
}
/**
* 启动所有任务
* @return
*/
@RequestMapping("/_startAll")
fun startAllJobs(): HttpStatus {
jobService.startAllJobs()
return HttpStatus.OK
}
/**
* 暂停所有任务
* @return
*/
@RequestMapping("/_pauseAll")
fun pauseAllJobs(): HttpStatus {
jobService.pauseAllJobs()
return HttpStatus.OK
}
/**
* 恢复所有任务
* @return
*/
@RequestMapping("/_resumeAll")
fun resumeAllJobs(): HttpStatus {
jobService.resumeAllJobs()
return HttpStatus.OK
}
/**
* 关闭所有任务
* @return
*/
@RequestMapping("/_shutdownAll")
fun shutdownAllJobs(): HttpStatus {
jobService.shutdownAllJobs()
return HttpStatus.OK
}
}
6. 运行
运行程序后,使用postman调用接口添加或执行任务
image.png image.png
网友评论