新的一年、舊的方式,這一次就從一個需求開發的角度和你們分享監控系統的開發。java
前段時間與你們分享了定時任務調用平臺xxl-job,也簡單地講了講平臺的結構模式、調度方法。web
【進階之路】定時任務調用平臺xxl-jobspring
調用任務的過程當中,若是xxl-job的代碼可以順利執行,可是自己須要執行的任務沒有順利執行成功,或者由於一些問題致使任務延遲執行甚至沒有執行,xxl-job並不會正常報錯通知。這個時候,咱們就須要用一些其餘的方法來協助監控定時任務的執行。數據庫
在大佬的要求下,我這邊設計了一個方案,如圖所示:json
定時任務監控體系分爲三個部分(其實若是將消息中間件換成異步請求也能夠,只是在處理任務比較多又比較集中的時候,對監控系統的壓力比較大,監控系統自己業務無關,是不該該佔用過多的系統資源的)。設計模式
/** * 根據業務需求、須要對以前的業務進行埋點處理,主要是對定時任務的場景進行處理, 這邊採起的方法是結構型設計模式,儘可能依託本來的功能、減小代碼侵入, 使得消息實現與通知與本來的業務內容少耦合 **/ @Slf4j public abstract class AbstractTestFileComponent implements ITestFileComponent, TaskWarnService { //單純的MQ推送 @Autowired private QueueSender queueSender; @Override public boolean dealFile(Object obj){ //一、記錄任務耗時 StopWatch sw = new StopWatch(); sw.start(); try { //二、這裏生成通知信息 dealExe指的是本來執行任務的模塊 TaskDetailsDto taskDtl = this.dealExe(obj); taskDtl.setParcTime(DateUtil.getFormatDate()); taskDtl.setStatus(1); sw.split(); taskDtl.setConsTime(new Long(sw.getSplitTime()).intValue()); // 三、須要提交時,在子類重寫些方法 並填充code 併發送 this.submitResult(taskDtl); return true; } catch(ServiceException e){ //記錄日誌、而且在任務報錯的時候記錄信息 log.error(this.getMethodDesc(), obj, e); TaskDetailsDto taskDtl =new TaskDetailsDto(); taskDtl.setParcTime(DateUtil.getFormatDate()); taskDtl.setStatus(3); sw.split(); taskDtl.setConsTime(new Long(sw.getSplitTime()).intValue()); this.submitResult(taskDtl); } return true; } @Override public void submitResult(TaskDetailsDto taskDtl) { //四、在子類補完信息 this.fillTaskCode(taskDtl); if (!TextUtils.isEmpty(taskDtl.getIdentify())) { // MQ發送信息、根據業務要求、若是沒用規定執行時間則取當天日期 if (Objects.isNull(taskDtl.getOpeDate())){ taskDtl.setOpeDate(DateUtil.getDate(new Date())); } queueSender.send("loanMonitor.taskWarn", JSON.toJSONString(taskDtl)); log.info("loanMonitor.taskWarn mq send taskDetailsDto:{}",taskDtl); } else { log.info("taskDtl.getIdentify is null,TaskDetailsDto:{}", taskDtl); } } @Override public TaskDetailsDto fillTaskCode(TaskDetailsDto taskDtl) { //在子類中肯定執行任務信息、執行時間與主鍵ID taskDtl.setGeneralSts(msg); taskDtl.setIdentify(id); taskDtl.setOpeDate(date); log.info("taskDtl:{}",taskDtl); return taskDtl; } public abstract TaskDetailsDto dealExe(String obj); public abstract String getMethodDesc(); } public interface TaskWarnService { /** * 這裏能夠理解爲獲取任務的ID、執行內容等數據、能夠在代碼中實現 */ TaskDetailsDto fillTaskCode(TaskDetailsDto taskDtl); /** * 上送事件 警告單 * @param taskDtl */ void submitResult(TaskDetailsDto taskDtl); }
執行任務模式,我採用的是橋接模式,將抽象的類與實現類分離,使它們能夠獨立變化。咱們在本身設計的過程當中,也能夠根據不一樣的狀況採用不一樣的方法來實現信息推送的功能。springboot
在此模塊中,主要目的是要可以準確的獲取任務執行狀況,而後將任務推送給指定的MQ,內部記錄的數據能夠根據本身的要求來肯定,可是不推薦將那種一天內須要很是屢次輪訓的任務也進行監控。微信
定時任務監控系統中,主要須要實現如下幾個功能:併發
//首先要獲取到MQ的信息,在springboot中可快捷的實現 @JmsListener(destination = "loanMonitor.taskWarn") public void dealTaskJob(String data) { TaskDetailsDto taskDetailsDto = JacksonUtils.jsonToObject(data, TaskDetailsDto.class); //一、參數判斷、能夠參考個人開源項目溫蒂,這個方法就是複用了wendi(溫蒂)中的參數處理方法、能夠根據不一樣狀況處理不一樣的需求 checkParamTaskDetails(taskDetailsDto); log.info("dealTaskJobMq已經啓動,Identify:{}", taskDetailsDto.getIdentify()); //二、查詢數據庫、在各個實際接口中查詢是否新增了登記任務、taskTempDto爲所執行任務的任務清單 TaskTempDto taskTempDto = new TaskTempDto(); taskTempDto.setIdentify(taskDetailsDto.getIdentify()); taskTempDto = taskTempService.queryByIdentify(taskTempDto); TaskDetailsDto queryTaskJob = taskDetailsService.queryByIdDate(taskDetailsDto); /* 三、無需執行的狀況: I、是否須要操做爲否 。 II、結果已經處理 。 III、已經報警且結果爲正常或者過時、同時執行時間不爲空 IV、重複報警通知 */ if (無需執行狀況) return; } //四、若是狀態不是正常執行,直接報警 if (條件1) { //調用郵箱通知 //調用短信通知 //五、若是狀態是正常執行,則判斷是否須要報警同時 } else if (條件2) { //根據不一樣的任務,執行不一樣的操做、具體由taskTempDto裏的數據來肯定 } log.info("dealTaskJobMq執行:{},Identify:{}", b, taskDetailsDto.getIdentify()); }
這一點我主要考慮使用定時任務來解決問題,並且不須要考慮再次監控的問題(否則就無限套娃了)。運維
生成清單的時候,要考慮不一樣任務的場景,好比有的任務是一天分批次執行(好比一些批量任務),有的任務是天天執行一次(好比對帳任務),還有的任務是幾天甚至更長時間才執行一次(好比周的差錯)。這個時候,生成任務清單,包括處理任務清單的時候也須要考慮到,這裏我就本身的任務給你們分享一下個人任務清單。
@Data public class TaskTempDto implements Serializable { /** * 主鍵 */ private Integer id; /** *任務包名稱,主要展現用 */ private String taskName; /** *標識、也是每一個任務的主鍵 */ private String identify; /** *類型(1重複/2動態/3周/4月) *不一樣的類型決定不一樣的場景,通常能夠採用枚舉的方式 */ private Enum status; /** *歸屬模塊、分類 */ private String moudule; /** *是否須要報警 一、報警 2不報警、能夠決定是否須要被通知 */ private Integer alertor; /** *是否須要操做 1是 2否、決定任務是否須要執行 */ private Integer operate; /** * 計劃執行時間 */ private String schdTime; /** * 執行延時範圍、能夠用來監控執行是否超時 */ private Integer rangTime; /** * 郵箱,配置通知郵箱 */ private String mail; /** * 配置通知的手機短信,這裏能夠用[],也能夠直接分割 */ private String mobile; /** *操做日期,決定通知來的日期是否正確 */ private String opeDate; /** *操做人 */ private String oper; }
衆所周知,若是沒有一個漂亮而且看讓人看一眼就會能徹底掌握使用方法的頁面,那就表明開發人員須要本身來操做進行模板數據的增減修改,這無疑是很不可取的,因此,一個完善的任務監控系統也須要一個完善的UI控制界面,不只方便運維人員操做,也能夠清晰地展現每一個任務的執行狀況與執行效率,報警的任務須要負責人員進行處理並手動解除警報,這樣,一個土生土長地任務監控系統就完成了。
若是任務量小、且多爲單機任務單、亦或是項目中沒有消息中間件的話,能夠嘗試使用http請求(針對非分佈式)或者聲明式的web service(feign),這樣只須要將監控系統部署在私服中,再引入須要的項目中便可。
在定時任務執行成功以後,開啓一個線程來調用就能解決問題。固然,在設計之初我也考慮了這個問題,因此預留了接口有備無患。
由於業務過於耦合的問題,能夠考慮使用切面進行開發,不過目前的線上的定時任務並不須要24小時執行,因此我沒有選擇這個方案(偷懶了),可是在開發前期也在部分接口中使用了切面進行開發。
對比橋接模式,切面的開發方法對於代碼的侵入大幅降低,可是代碼的複用性會下降,由於針對不一樣的任務須要考慮不一樣的執行方案。
同時,由於使用切面難以對複雜的定時任務項目進行開發: 業務並非二極管,只有成功與失敗,還有各類各樣的狀況,就拿我熟悉的還款計劃來講,有計劃已經執行,有計劃改變,甚至某一條計劃出現問題,這些狀況同樣是執行成功,可是使用切面方法就很難掌握具體狀況了。
何時須要報警,這是一個問題。
以前在代碼中,我設計瞭如下狀況做直接忽略報警,並且都是在實際的生產中遇到的一些須要忽略警情的問題:
你們好,我是練習java兩年半時間的南橘,下面是個人微信,須要以前的導圖或者想互相交流經驗的小夥伴能夠一塊兒互相交流哦。
有須要的同窗能夠加個人公衆號,之後的最新的文章第一時間都在裏面,也能夠找我要思惟導圖