quartz监控日志(一)

2019-08-02 11:10:39 浏览数 (2)

最近几个月,现网总是出现定时器不执行的情况,或者定时器卡死的情况,而又不方便排查,只能依靠quartz的debug日志以及错误日志来监控定时器的执行情况,并且随着我们系统中job越来越多,而使得job问题越来越难以跟踪,所以我们才需要一个能过对定时器进行监控的功能,并能实现线程阻塞告警,以及杀死阻塞线程的功能。

监控job有几种方案:

方案一:通过jmx远程或者直接在应用内部定时获取quartz执行信息,可以新增、修改job、job触发器以及执行情况,但是无法对以前执行的job进行跟踪。

方案二:在job的实现类中记录日志,这个方案太麻烦,因为系统目前有很多job实现类,不可能每个都去添加日志。

方案三:代理job执行类,在初始化时使用代理job执行器。

最后我选择了方案三。

先让我们来分析下源码,目前只针对quartz1.6.0:

首先查看JobRunShell类,这个是定时器的执行类实现了Runnable接口,它有两个空方法如下:

代码语言:javascript复制
public class JobRunShell implements Runnable {
   public void run() {
      //省略若干代码
             try {
                begin();
            } catch (SchedulerException se) {
                qs.notifySchedulerListenersError("Error executing Job ("
                          jec.getJobDetail().getFullName()
                          ": couldn't begin execution.", se);
                break;
            }
      //省略若干代码
           try {
                complete(true);
            } catch (SchedulerException se) {
                qs.notifySchedulerListenersError("Error executing Job ("
                          jec.getJobDetail().getFullName()
                          ": couldn't finalize execution.", se);
                continue;
            }
       }
  protected void begin() throws SchedulerException {
    }

    protected void complete(boolean successfulExecution)
        throws SchedulerException {
    }

}

很明显,这里预留了两个方法来监控job的执行情况。

所以我们创建了一个其子类来代理它,在开始时记录日志,结束时更新日志,

代码语言:javascript复制
public class MonitorJobRunShell extends JobRunShell {
     
    /**    
     * 创建一个新的实例 JobRunShellImpl.  
     * @param jobRunShellFactory
     * @param scheduler
     * @param schdCtxt    
     */
    public MonitorJobRunShell(JobRunShellFactory jobRunShellFactory, Scheduler scheduler, SchedulingContext schdCtxt) {
        super(jobRunShellFactory, scheduler, schdCtxt);
    }

    @Override
    protected void begin() throws SchedulerException {
        super.begin();
        try {
            JobDetail jobDetail = jec.getJobDetail();
            quartzLog=getService().insert(jobDetail.getName());
        } catch (Exception e) {
            logger.error("记录job开始时间异常",e);
        }catch (Throwable e) {
            logger.error("记录job开始时间出错",e);
        }
        
    }

    @Override
    protected void complete(boolean successfulExecution) throws SchedulerException {
        super.complete(successfulExecution);
        try {
            quartzLog.setExeTime(jec.getJobRunTime());
            getService().update(quartzLog);
        } catch (Exception e) {
            logger.error("记录job结束时间异常",e);
        }catch (Throwable e) {
            logger.error("记录job结束时间出错",e);
        }
    }
}

创建了该类,必须要让quartz使用我们创建的代理类,这里quartz使用了简单工厂模式,如下

代码语言:javascript复制
public interface JobRunShellFactory {
  /**
     * <p>
     * Called by the <code>{@link org.quartz.core.QuartzSchedulerThread}</code>
     * to obtain instances of <code>{@link JobRunShell}</code>.
     * </p>
     */
    JobRunShell borrowJobRunShell() throws SchedulerException;

}

我们只需要实现该接口,代理原有的std工厂类:

代码语言:javascript复制
public class StdJobRunShellFactoryProxy implements JobRunShellFactory{
  

    /**
     * <p>
     * Called by the <class>{@link org.quartz.core.QuartzSchedulerThread}
     * </code> to obtain instances of <code>
     * {@link org.quartz.core.JobRunShell}</code>.
     * </p>
     */
    public JobRunShell borrowJobRunShell() throws SchedulerException {
        return new MonitorJobRunShell(this, scheduler, schedCtxt);
    }

    /**
     * <p>
     * Called by the <class>{@link org.quartz.core.QuartzSchedulerThread}
     * </code> to return instances of <code>
     * {@link org.quartz.core.JobRunShell}</code>.
     * </p>
     */
    public void returnJobRunShell(JobRunShell jobRunShell) {
        jobRunShell.passivate();
    }
}

进行到这里,需要使用到我们的工厂代理类,这时候则需要代理入口,即StdSchedulerFactory,

代码语言:javascript复制
public class StdSchedulerFactoryProxy extends StdSchedulerFactory {

    
    /**
     * 初始化Scheduler
     * 同时,替换JobRunShellFactory,并启动清理job日志线程
     * @see org.quartz.impl.StdSchedulerFactory#instantiate(org.quartz.core.QuartzSchedulerResources, org.quartz.core.QuartzScheduler)
     */
    protected Scheduler instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs) {
        SchedulingContext schedCtxt = new SchedulingContext();
        schedCtxt.setInstanceId(rsrcs.getInstanceId());
        Scheduler scheduler = new StdScheduler(qs, schedCtxt);
        try {
            JobRunShellFactory jobFactory=new StdJobRunShellFactoryProxy();
            jobFactory.initialize(scheduler, schedCtxt);
            rsrcs.setJobRunShellFactory(jobFactory);
        } catch (SchedulerConfigException e) {
            logger.error("初始化MonitorStdJobRunShellFactory出错",e);
        }
        return scheduler;
    }
}

最后在初始化Scheduler时使用我们代理的Scheduler工厂类就行,实例如下:

StdSchedulerFactory factory = new StdSchedulerFactoryProxy();

这里我们就实现了自己的quartz监控程序,日志记录方式可以自己扩展。这样可以有效方便的监控job的执行情况,日志中可以记录job的执行时长、线程id等,可以配置阈值如果超时可以在界面上kill该线程。

0 人点赞