quartz監控日誌(一)
最近幾個月,現網總是出現定時器不執行的情況,或者定時器卡死的情況,而又不方便排查,只能依靠quartz的debug日誌以及錯誤日誌來監控定時器的執行情況,並且隨著我們系統中job越來越多,而使得job問題越來越難以跟蹤,所以我們才需要一個能過對定時器進行監控的功能,並能實現執行緒阻塞告警,以及殺死阻塞執行緒的功能。
監控job有幾種方案:
方案一:通過jmx遠端或者直接在應用內部定時獲取quartz執行資訊,可以新增、修改job、job觸發器以及執行情況,但是無法對以前執行的job進行跟蹤。
方案二:在job的實現類中記錄日誌,這個方案太麻煩,因為系統目前有很多job實現類,不可能每個都去新增日誌。
方案三:代理job執行類,在初始化時使用代理job執行器。
最後我選擇了方案三。
先讓我們來分析下原始碼,目前只針對quartz1.6.0:
首先檢視JobRunShell類,這個是定時器的執行類實現了Runnable介面,它有兩個空方法如下:
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的執行情況。
所以我們建立了一個其子類來代理它,在開始時記錄日誌,結束時更新日誌,
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該執行緒。