项目实战给定时器配置监听不使用数据库

前言

有时候任务执行可能会延迟,这里我们主要检测下一下延迟超过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、测试成功

源码下载





感兴趣的,欢迎添加博主微信,

哈,博主很乐意和各路好友交流,如果满意,请打赏博主任意金额,感兴趣的在微信转账的时候,备注您的微信或者其他联系方式。添加博主微信哦。


请下方留言吧。可与博主自由讨论哦

微信 微信公众号 支付宝
微信 微信公众号 支付宝
posted @ 2019-02-21 15:54  HealerJean  阅读(198)  评论(0编辑  收藏  举报