项目实战给定时器配置监听不使用数据库
前言
有时候任务执行可能会延迟,这里我们主要检测下一下延迟超过5分钟的任务,然后发送给管理员,告诉他有任务延迟了
这个任务量很小,所有就没有必要放到数据库中存储任务了,使用ram存储
1、相关数据表
数据表在上两节有介绍
qrtz_job_details 工作详情表
qrtz_triggers 触发器任务执行表
2、quartz 配置文件
#配置线程池的容量,即表示同时最多可运行的线程数量
org.quartz.threadPool.threadCount = 20
org.quartz.scheduler.skipUpdateCheck = true
#scheduler实例名称。
org.quartz.scheduler.instanceName = HealerJeanQuartzScheduler
org.quartz.scheduler.jobFactory.class = org.quartz.simpl.SimpleJobFactory
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
#job存储方式,RAMJobStore是使用JobStore最简单的一种方式,它也是性能最高效的,顾名思义,JobStore是把它的数据都存储在RAM中,
# 这也是它的快速和简单配置的原因;JDBCJobStore也是一种相当有名的JobStore,它通过JDBC把数据都保存到数据库中,
# 所以在配置上会比RAMJobStore复杂一些,而且不像RAMJobStore那么快,但是当我们对数据库中的表的主键创建索引时,性能上的缺点就不是很关键的了。
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
3、开始配置定时器Schdule
package com.hlj;
import com.hlj.quartz.core.event.QuartzListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 类描述:
* 创建人: HealerJean
*/
@Configuration
public class ListenerConfig {
/**
* 配置定时器初始化
* @return
*/
@Bean
public QuartzListener quartzListener(){
return new QuartzListener();
}
}
3.1、初始化Schdule
package com.hlj.quartz.core.event;
import com.hlj.quartz.core.schedule.HealerJeanScheduler;
import org.quartz.SchedulerException;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.quartz.impl.StdSchedulerFactory;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.annotation.WebListener;
/**
* web应用启动的时候执行,初始化quartz
* QuartzInitializerListener implements ServletContextListener,所以可以用下面的,或者我们自己也可以设置其他方式,只要让它启动的时候执行
*/
@WebListener
public class QuartzListener extends QuartzInitializerListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
super.contextInitialized(servletContextEvent);
// 可以使用下面的传入,但是我认为还是没有必要了
// schedulerFactory会自动装入ServletContext 可以使用schedulerFactory.get
// ServletContext ctx = servletContextEvent.getServletContext();
// StdSchedulerFactory schedulerFactory = (StdSchedulerFactory) ctx.getAttribute(QUARTZ_FACTORY_KEY);
// HealerJeanScheduler.initialise(schedulerFactory);
HealerJeanScheduler.initialise();
}
}
4、定时器配置以及添加任务监听
package com.hlj.quartz.core.schedule;
import com.hlj.quartz.core.event.HealerJeanJobListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class HealerJeanScheduler {
private static Logger logger = LoggerFactory.getLogger(HealerJeanScheduler.class);
private final static String TRIGGER_PERFIX = "t_";
private Scheduler scheduler = null;
private static HealerJeanScheduler instance = null;
private HealerJeanScheduler(){
}
private HealerJeanScheduler(Scheduler scheduler){
this.scheduler = scheduler;
}
public static HealerJeanScheduler getInstance(){
if (instance == null){
logger.error("AdmoreScheduler not initialized");
throw new RuntimeException("AdmoreScheduler not initialized");
}
return instance;
}
/**
* 初始化一个定时器调度器
*/
// public static void initialise(StdSchedulerFactory stdSchedulerFactory){
public static void initialise(){
if (instance == null){
synchronized (HealerJeanScheduler.class){
if (instance == null){
try {
//下面这个会自动读取配置文件中的数据 名字为 HealerJeanquartzScheduler
// Scheduler scheduler = stdSchedulerFactory.getScheduler();
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler() ;
//添加任务监听
scheduler.getListenerManager().addJobListener(new HealerJeanJobListener());
instance = new HealerJeanScheduler(scheduler);
logger.info("Scheduler init complete");
} catch (SchedulerException e) {
logger.error("Scheduler init failed" , e );
throw new RuntimeException("Scheduler init failed" + e.getCause(),e);
}
}
}
}
}
public <T extends Job> void startJob(String jobId, String cron, Class<T> t) throws SchedulerException {
JobDetail job = JobBuilder.newJob(t).withIdentity(jobId).build();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(TRIGGER_PERFIX + jobId).withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
scheduler.scheduleJob(job, trigger);
logger.info(jobId + " start at " + new Date());
}
}
4.2、任务监听
package com.hlj.quartz.core.event;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
/**
* 类描述:
* 创建人: HealerJean
* job任务监听,在 HealerJeanScheduler 里面配置
*/
@Slf4j
public class HealerJeanJobListener implements JobListener {
private static final String LISTENER_NAME = "healerjean.job.listener";
@Override
public String getName() {
return LISTENER_NAME;
}
/**
* 任务执行之前执行
*/
@Override
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
log.info(jobExecutionContext.getJobDetail().getKey() + " to be execute");
}
/**
*
* 任务取消
* 这个方法正常情况下不执行,但是如果当 TriggerListener中的 vetoJobExecution 方法返回true时, 那么执行这个方法.
* 需要注意的是 如果方法(2)执行 那么(1),(3)这个俩个方法不会执行,因为任务被终止了嘛.
*/
@Override
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
log.info(jobExecutionContext.getJobDetail().getKey() + " vetoed");
}
/**
任务执行完成后执行,jobException如果它不为空则说明任务在执行过程中出现了异常
*/
@Override
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
if (e != null){
String jobName = jobExecutionContext.getJobDetail().getKey().getName();
String errMsg = e.getMessage();
log.error(jobExecutionContext.getJobDetail().getKey() + " execute failure");
log.error(e.getMessage(),e);
} else {
log.info(jobExecutionContext.getJobDetail().getKey() + " execute success");
}
}
}
5、Job任务
package com.hlj.quartz.job;
import com.hlj.service.QuartzCheckService;
import com.hlj.utils.SpringHelper;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
/**
* @DisallowConcurrentExecution 禁止并发执行多个相同定义的JobDetail,
* 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义),
* 但是可以同时执行多个不同的JobDetail,
* 举例说明,我们有一个Job类,叫做SayHelloJob, 并在这个Job上加了这个注解, 然后在这个Job上定义了很多个JobDetail,
* 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail,
* 那么当scheduler启动时, 不会并发执行多个sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail,
*/
@DisallowConcurrentExecution //一般情况下建议加上
public class QuartzCheckJob implements Job {
public static final String JOB_KEY = "quartz.job.check";
private Logger logger = LoggerFactory.getLogger(QuartzCheckJob.class);
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
QuartzCheckService quartzCheckService = SpringHelper.getBean(QuartzCheckService.class);
Calendar calendar = Calendar.getInstance();
//查询之前5分钟的job本应该开始的job
calendar.add(Calendar.MINUTE,-5);
quartzCheckService.checkQuartzJob(calendar);
logger.info("job check process time:"+ Calendar.getInstance().getTime());
} catch (Exception e) {
logger.error("[quartz.job.check]" + e.getMessage(),e);
}
}
}
6、任务实现
6.1、任务接口
package com.hlj.service;
import com.hlj.data.general.AppException;
import java.util.Calendar;
/**
*
* @version 1.0.0
*/
public interface QuartzCheckService {
/**
* 检查任务列表
* @param calendar
*/
void checkQuartzJob(Calendar calendar) throws AppException;
}
6.2、任务实现接口
package com.hlj.service.impl;
import com.hlj.dao.mybatis.check.QuartzDbProcessCheckMapper;
import com.hlj.data.general.AppException;
import com.hlj.data.res.check.JobDetailData;
import com.hlj.data.res.check.QuartzCheckData;
import com.hlj.service.QuartzCheckService;
import com.hlj.utils.DateHelper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Calendar;
import java.util.List;
/**
* 类名称:
* 类描述:
* 创建人:HealerJean
*
* @version 1.0.0
*/
@Service
public class QuartzCheckServiceImpl implements QuartzCheckService {
private Logger logger = LoggerFactory.getLogger(QuartzCheckServiceImpl.class);
@Resource
private QuartzDbProcessCheckMapper quartzDbProcessCheckMapper;
@Override
public void checkQuartzJob(Calendar calendar) throws AppException {
Long current = Calendar.getInstance().getTimeInMillis();
Long time = calendar.getTimeInMillis();
List<QuartzCheckData> checkDataList = quartzDbProcessCheckMapper.findJobList(time);
if(CollectionUtils.isEmpty(checkDataList)){
logger.info("quartz monitor:没有延迟job,非常好!"+Calendar.getInstance().getTime());
}else{
for(QuartzCheckData checkData:checkDataList){
if(current.compareTo(checkData.getNextFireTime()) > 0){
logger.error("jobName:"+checkData.getJobName()+",延迟:"+((current-checkData.getNextFireTime())/1000)+"秒");
JobDetailData detailData = quartzDbProcessCheckMapper.findJobDetailData(checkData.getJobName());
String desc = "";
if(detailData != null){
desc = detailData.getDescription();
}
String text = "["+checkData.getJobName()+":"+desc+"]延迟:"+processTime(current,checkData.getNextFireTime())+"尚未执行,信息发送时间:"+ DateHelper.convertDate2String(Calendar.getInstance().getTime(),DateHelper.YYYY_MM_DD_HH_MM_SS);
//发送邮件
logger.error(text);
}
}
}
}
private String processTime(Long currtime,Long ptime){
Long minus = (currtime - ptime)/1000;
if(minus < 60){
return minus +"秒";
}else if(minus >=60 && minus < 3600){
return (minus/60)+"分" + (minus%60)+"秒";
}else{
return (minus/3600) + "小时" + ((minus%3600)/60)+"分" + (minus%60)+"秒";
}
}
}
7、查询操作
7.1、mapper
package com.hlj.dao.mybatis.check;
import com.hlj.data.res.check.JobDetailData;
import com.hlj.data.res.check.QuartzCheckData;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 类名称:QuartzDbProcessCheckMapper
* 类描述:job启动检查任务
* 创建人:HealerJean
* 修改人:
* 修改备注:
*
* @version 1.0.0
*/
public interface QuartzDbProcessCheckMapper {
/**
* 查看本应该在 现在的时间之前开始的任务
*/
List<QuartzCheckData> findJobList(@Param("next_fire_time") Long time);
/**
* 根据工作名 查找工作详情
* @param jobName
* @return
*/
JobDetailData findJobDetailData(@Param("job_name") String jobName);
}
7.2、查询sql
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hlj.dao.mybatis.check.QuartzDbProcessCheckMapper">
<select id="findJobList" resultType="com.hlj.data.res.check.QuartzCheckData">
SELECT
A1.`SCHED_NAME` AS `SCHEDNAME`,
A1.`TRIGGER_NAME` AS `TRIGGERNAME`,
A1.`TRIGGER_GROUP` AS `TRIGGERGROUP`,
A1.`JOB_NAME` AS `JOBNAME`,
A1.`JOB_GROUP` AS `JOBGROUP`,
A1.`DESCRIPTION` AS `DESCRIPTION`,
A1.`NEXT_FIRE_TIME` AS `NEXTFIRETIME`,
A1.`PREV_FIRE_TIME` AS `PREVFIRETIME`,
A1.`START_TIME` AS `STARTTIME`,
A1.`END_TIME` AS `ENDTIME`
FROM `qrtz_triggers` A1
<![CDATA[
WHERE A1.TRIGGER_STATE != 'PAUSED'
AND A1.`NEXT_FIRE_TIME` <= #{next_fire_time}
]]>
</select>
<select id="findJobDetailData" resultType="com.hlj.data.res.check.JobDetailData">
SELECT
A1.`SCHED_NAME` AS `SCHEDNAME`,
A1.`JOB_NAME` AS `JOBNAME`,
A1.`JOB_GROUP` AS `JOBGROUP`,
A1.`DESCRIPTION` AS `DESCRIPTION`,
A1.`JOB_CLASS_NAME` AS `JOBCLASSNAME`
FROM `qrtz_job_details` A1
WHERE A1.`JOB_NAME` = #{job_name}
LIMIT 0, 1
</select>
</mapper>
8.1、应用启动即执行上面任务
package com.hlj;
import com.hlj.quartz.core.schedule.HealerJeanScheduler;
import com.hlj.quartz.job.QuartzCheckJob;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
/**
* 类名称:StrartJob
* 类描述:集中用来启动进行监控job执行
* 修改备注:
*/
@Service
public class StrartJob implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(StrartJob.class);
@Override
public void run(String... args) throws Exception {
try {
//每5分钟执行job监控
HealerJeanScheduler.getInstance().startJob(QuartzCheckJob.JOB_KEY,"0 0/5 * * * ?", QuartzCheckJob.class);
} catch (SchedulerException e) {
e.printStackTrace();
logger.error("启动job报错:"+e.getMessage(),e);
}
}
}
9、测试成功
源码下载
感兴趣的,欢迎添加博主微信,
哈,博主很乐意和各路好友交流,如果满意,请打赏博主任意金额,感兴趣的在微信转账的时候,备注您的微信或者其他联系方式。添加博主微信哦。
请下方留言吧。可与博主自由讨论哦
微信 | 微信公众号 | 支付宝 |
---|---|---|
![]() |
![]() |
![]() |