定时任务监控(一)-Quartz

定时任务监控(二)-xxl-job

前言

为了方便对对公司的定时任务进行监控与管理,决定对定时任务服务进行改造升级,开发一个可视化的定时任务监控管理平台。由于公司定时任务是使用的是Quartz,所以我们首先对Quartz的监控管理平台进行了预研。
这里先说一下最后的结果,选用的是xxl-job替代Quartz。期间还预研了elastic-job,在多方对比之下选择了使用xxl-job替换Quartz。这样做的理由是

  • 替换成本低,定时任务逻辑方法不用做任何代码更改
  • xxl-job提供自带的定时任务监控管理界面
  • 对于elastic-job的分片功能,xxl-job也有自己的一套实现方法支持
  • 上手简单,官方文档全面(这个是真的上手简单,看了三四个小时官方文档加自己动手demo实践,基本就掌握了)

正文

虽说最后没用继续使用Quartz,不过对于Quartz的监控管理也不是一无所获,下面介绍一下Quartz的监控管理。主要的方式有两种

  • 基于jmx的远程管理quartz-monitor
  • 直接对定时任务进行改造,通过Quartz的Scheduler对象获取定时任务信息即对定时任务进行调度控制

我们主要是对第二种方式进行了预研。做的demo效果如下:
在这里插入图片描述

quartz任务持久化至数据库

首先Quartz是提供了定时任务信息持久化到数据库的功能的,在quartz.properties中有这样一个配置

# 默认存储在内存中
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

默认或则没有配置的情况下都是存储在内存之中,但是我们可以去官方文档中拿到表结构,建表后在quartz.properties进行配置数据库信息,这样就能直接读取数据库获取Quartz定时任务的实时状态了。
建表sql:

#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set 
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#

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)
);

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)
);

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)
);

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)
);

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)
);

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)
);

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)
);

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(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)
);

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)
);

CREATE TABLE QRTZ_LOCKS
  (
    SCHED_NAME VARCHAR(120) NOT NULL,
    LOCK_NAME  VARCHAR(40) NOT NULL, 
    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
commit;

配置信息
在这里插入图片描述

# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 5000

# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.dataSource = qzDS

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.qzDS.URL = jdbc:mysql://127.0.0.1:3306/quartz?useUnicode=true&characterEncoding=UTF-8

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = 123456

org.quartz.dataSource.qzDS.maxConnections = 10

进行了上述配置之后,定时任务每次执行时都会将最新的任务相关信息更新入表中,我们做订单任务监控页面数据来源就是这些表中数据了。

1、QRTZ_JOB_DETAILS:存储的是job的详细信息,包括:[DESCRIPTION]描述,[IS_DURABLE]是否持久化,[JOB_DATA]持久化对象等基本信息。

2、QRTZ_TRIGGERS:触发器信息,包含:job的名,组外键,[DESCRIPTION]触发器的描述等基本信息,还有[START_TIME]开始执行时间,[END_TIME]结束执行时间,[PREV_FIRE_TIME]上次执行时间,[NEXT_FIRE_TIME]下次执行时间,[TRIGGER_TYPE]触发器类型:simple和cron,[TRIGGER_STATE]执行状态:WAITING,PAUSED,ACQUIRED分别为:等待,暂停,运行中。

3、QRTZ_CRON_TRIGGERS:保存cron表达式。

4、QRTZ_SCHEDULER_STATE:存储集群中note实例信息,quartz会定时读取该表的信息判断集群中每个实例的当前状态,INSTANCE_NAME:之前配置文件中org.quartz.scheduler.instanceId配置的名字,就会写入该字段,如果设置为AUTO,quartz会根据物理机名和当前时间产生一个名字。[LAST_CHECKIN_TIME]上次检查时间,[CHECKIN_INTERVAL]检查间隔时间。

5、QRTZ_PAUSED_TRIGGER_GRPS:暂停的任务组信息。

6、QRTZ_LOCKS,悲观锁发生的记录信息。

7、QRTZ_FIRED_TRIGGERS,正在运行的触发器信息。

8、QRTZ_SIMPLE_TRIGGERS,简单的出发器详细信息。

9、QRTZ_BLOB_TRIGGERS,触发器存为二进制大对象类型(用于Quartz用户自己触发数据库定制自己的触发器,然而JobStore不明白怎么存放实例的时候)。

那么做了上面这些操作之后,我们就可以通过查询数据库可以获取到下图中的数据
在这里插入图片描述

quartz对定时任务操作(启动、暂停、修改cron等)

quartz是的定时任务是依靠触发器去执行的,而触发器则是由Scheduler进行调度和管理。因此我们只需要在类中使用@Autowired获取到Scheduler,使用Scheduler提供的一系列方法就能对定时任务进行操作。下面列举一些我们可能会用到的方法:

//jobClassName表示job任务的类名,jobGroupName表示任务组
//这些在配置完上面的持久化后都能在数据库中查询到

@Autowired
private Scheduler scheduler;
...
//暂停任务(使定时任务后续到达执行时间时不再运行,对当前有正在运行中的定时任务线程没有影响)
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));

//恢复被暂停的任务
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));

//删除任务需要先暂停触发器、停止对触发器的调度后再删除任务
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));


//添加新的任务除了需要jobClassName和jobGroupName外还需要一个参数cronExpression即cron表达式
//jobGroupName即可以是已有的任务组,也可以自己新取一个任务组(就随便取一名,quartz去帮你建)
// 启动调度器  
scheduler.start(); 	
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
            .withSchedule(scheduleBuilder).build();
try {
	scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
    System.out.println("创建定时任务失败:"+e);
}



//修改定时任务(主要是修改cron表达式)
try {
	TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
	// 表达式调度构建器
	CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
	CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
	// 按新的cronExpression表达式重新构建trigger
	trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
	// 按新的trigger重新设置job执行
	scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
	System.out.println("更新定时任务失败"+e);
	throw new Exception("更新定时任务失败");
}

监控任务整体设计

在有了上述的预研结果之后,我们就开始着手对公司的定时任务的改造进行设计。我们想的是做的公用一点、复用性强一点,最好做成一个公共组件,让别的研发部门也能直接引用我们的成果。于是就有了下面的设计:

  • 首先监控页面做成一个单独的服务出来,对定时任务信息的查询可以通过数据库获取

  • 对于定时任务的操作(暂停、修改等)想到了两个方案
    一、通过feign调用,调到定时任务项目里去进行操作
    二、新建一个表,将操作及操作信息存放进去。在定时任务项目加一个定时任务,专门读取新表中的数据,完成相应的操作。操作结果及报错信息等可以记录在备注字段等以供反馈。

  • 可以将对定时任务项目中的改造做成jar包,以后有新的quartz项目接入进来,只需引入jar包配置相关配置文件即可

  • 之所以将监控页面单独做一个服务提出来也是因为这样可以方便上面jar包的引入,减少耦合和重复性工作。且可以进行多数据源配置及引入公司的鉴权体系,用户在登录之后弹出数据源选择页,在进行数据源选择后进入任务监控管理界面,对对应的定时任务项目进行监控管理。

参考Quartz定时器可视化管理功能的简单实现
参考Spring Boot集成持久化Quartz定时任务管理和界面展示
参考简单的quartz可视化监听界面

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值