最近幾個月,現網老是出現定時器不執行的狀況,或者定時器卡死的狀況,而又不方便排查,只能依靠quartz的debug日誌以及錯誤日誌來監控定時器的執行狀況,而且隨着咱們系統中job愈來愈多,而使得job問題愈來愈難以跟蹤,因此咱們才須要一個能過對定時器進行監控的功能,並能實現線程阻塞告警,以及殺死阻塞線程的功能。java
監控job有幾種方案:ide
方案一:經過jmx遠程或者直接在應用內部定時獲取quartz執行信息,能夠新增、修改job、job觸發器以及執行狀況,可是沒法對之前執行的job進行跟蹤。this
方案二:在job的實現類中記錄日誌,這個方案太麻煩,由於系統目前有不少job實現類,不可能每一個都去添加日誌。線程
方案三:代理job執行類,在初始化時使用代理job執行器。debug
最後我選擇了方案三。代理
先讓咱們來分析下源碼,目前只針對quartz1.6.0:日誌
首先查看JobRunShell類,這個是定時器的執行類實現了Runnable接口,它有兩個空方法以下:code
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的執行狀況。blog
因此咱們建立了一個其子類來代理它,在開始時記錄日誌,結束時更新日誌,接口
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使用了簡單工廠模式,以下
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工廠類:
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,
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該線程。