在實際應用中,咱們常常會碰到這樣一些需求,例如天天統計數據,監控服務器狀態,按期清理緩存數據,或者天天給運營人員發送相關郵件。這時咱們就須要編寫計劃任務腳本並定時執行。javascript
weroll 提供了ScheduleManager 來實現簡單的計劃任務功能,目前能夠實現Timer任務和Daily任務兩種。html
Timer 任務 - 每隔一段時間執行一次前端
Daily 任務 - 天天在指定時間點執行一次java
接下來咱們來嘗試實現一個簡單的服務器性能監控的應用,用來監控機器的CPU和內存使用率,並以圖表的形式展現出來,天天凌晨的時候把前一天的監控狀況整理髮送郵件給運維人員。node
上一篇《快速搭建Node.js應用程序腳手架 (1)- 2分鐘Demo》咱們已經介紹了什麼是weroll,以及快速搭建一個Node.js應用。jquery
假設咱們已經建立好了工程,爲了更好的理解,我把最後完成時的工程目錄文件結構截圖展現出來:
git
如今,讓咱們開始編寫代碼!github
首先咱們須要在 ./server/schedule 目錄中建立一個腳本文件,咱們取名爲 monit.js,用來計算並記錄CPU和內存的使用率數據,這裏咱們使用了 cpu-stat 來得到CPU的使用率數據。web
/* ./server/schedule/monit.js */ var cpuStat = require('cpu-stat'); var os = require('os'); //exec方法是計劃任務腳本的入口函數 //當腳本執行完畢後,須要調用callBack(err)來結束本次任務 exports.exec = function(callBack, option) { //最多存儲幾回數據,默認30次 var max = option.max || 30; //每次監控的時間間隔,默認10秒一次 var duration = (option.duration || 10) * 1000; //計算CPU使用率的耗時,默認1秒,時間越長越精確 var checkTime = (option.checkTime || 1) * 1000; //這裏偷懶,把監控數據存儲在global對象裏 var Performance = global.Performance; if (!Performance) { //初始化監控數據 Performance = { cpu:[], mem:[] }; global.Performance = Performance; //由於不是持久化數據,爲了使圖表更好看 //這裏假設以前30次監控數據都爲0 var now = Date.now(); for (var i = 0; i < 30; i++) { var time = now - i * duration; Performance.cpu.push([ time, 0 ]); Performance.mem.push([ time, 0 ]); } } //使用cpu-stat得到CPU使用率數據 cpuStat.usagePercent({ sampleMs:checkTime }, function(err, cpuPercent) { if (err) { console.error(err); cpuPercent = 0; } var Performance = global.Performance; var time = Date.now(); //最多記錄max次監控數據, 把舊數據移除 if (Performance.cpu.length >= max) { Performance.cpu.shift(); } //記錄CPU使用率 Performance.cpu.push([ time, cpuPercent ]); console.log('cpu: ', cpuPercent, '%'); //計算內存使用率 var totalMem = os.totalmem(); var usedMem = totalMem - os.freemem(); var memPercent = 100 * usedMem / totalMem; //記錄內存使用率 Performance.mem.push([ time, memPercent ]); console.log('mem: ', memPercent, '%'); //本次任務完成 callBack(); }); }
monit.js 已經完成,接下來咱們須要配置它的執行規則。計劃任務的運行規則是在 ./server/config/localdev/schedule.json 中配置的:ajax
/* ./server/config/localdev/schedule.json */ { "ver": "1.0.0", "list":[ { "type":1, "duration":10, "script":"monit", "initExecute":true, "option":{ "duration":10, "checkTime":5, "max":8640 } } ] }
以上的配置定義了 monit.js 腳本是一個 Timer腳本 ,每隔10秒運行一次(應用啓動時會立刻運行一次),並設置了腳本使用參數:10秒一次計算,計算CPU使用率將耗時5秒,最多存儲8640次監控記錄(若是10秒一次的話,那麼就是最多保留24小時的監控數據)
腳本和配置已經完成了,如今咱們能夠啓用 ScheduleManager 來執行計劃任務了。一般在weroll應用程序中,咱們會在程序入口 main.js 啓動計劃任務:
/* ./main.js */ var App = require("weroll/App"); var app = new App(); //得到全局配置 ./server/config/localdev/setting.js var Setting = global.SETTING; app.addTask(function(cb) { //啓動web服務器 require("weroll/web/WebApp").start(Setting, function(webApp) { cb(); }); }); app.addTask(function() { //啓動計劃任務管理器 require("weroll/schedule/ScheduleManager").start(); }); //開始執行初始化 app.run();
如今咱們能夠運行程序看看計劃任務的執行效果。
$ node main.js -env=localdev -debug
通過上一步,咱們已經能夠在終端輸出監控數據了。接着咱們須要定義一個API,讓前端頁面經過ajax方式來獲取最新的監控數據。
在weroll應用程序中,API業務邏輯須要定義在 ./server/service 目錄中,這裏咱們取名爲 SystemService.js:
exports.config = { name: "system", //定義API的前綴名 enabled: true, //下面咱們定義並暴露了一個API方法 //完整的API名爲:system.performance security: { //@performance 得到服務器CPU和內存監控數據 "performance":{ needLogin:false } } }; exports.performance = function(req, res, params) { //獲取存儲在全局對象中的監控數據 var Performance = global.Performance || { cpu:[], mem:[] }; var result = {}; //只須要顯示最新的30次監控數據 for (var key in Performance) { result[key] = Performance[key].slice(-30); } //將數據返回給客戶端 res.sayOK(result); }
這樣咱們就定義了一個名爲 system.performance 的API,客戶端能夠經過HTTP POST方式來調用這個接口。下一步咱們會實現客戶端對API的調用。
詳細的API開發說明請閱讀 weroll - Guide: API
接着咱們還要實現一個web頁面,調用API獲取CPU和內存的監控數據,並用圖表的方式來顯示。前端圖表組件咱們使用 連接描述Chart.js,詳細使用方式請看它的官方文檔,這裏咱們不作詳述。
在weroll應用程序中,頁面路由須要定義在 ./server/router 目錄中,這裏咱們取名爲 index.js:
/* ./server/router/index.js */ function renderIndexPage(req, res, output, user) { output({ }); } exports.getRouterMap = function() { return [ { url: "/", view: "index", handle: renderIndexPage, needLogin:false }, { url: "/index", view: "index", handle: renderIndexPage, needLogin:false } ]; }
很是簡單,沒有任何邏輯。
接着是html頁面的實現,咱們在 ./client/views 目錄下建立 index.html :(請無視做者蹩腳的前端代碼)
<!-- ./client/views/index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Monit</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"> <!-- import js --> <script type="text/javascript" src="{{setting.RES_CDN_DOMAIN}}/js/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="{{setting.RES_CDN_DOMAIN}}/js/Chart.min.js"></script> </head> <body> <!-- CPU監控圖表 --> <div style="width:800px; height:300px;"> <canvas id="cpuChart" width="800" height="300"></canvas> </div> <!-- 內存監控圖表 --> <div style="width:800px; height:300px;"> <canvas id="memChart" width="800" height="300"></canvas> </div> <script> //定義Chart.js組件的樣式 var options = { scales: { yAxes: [{ display: true, ticks: { beginAtZero: true, steps:20, stepValue:5, max:100 } }] } }; var cpuChartData = { labels: [], datasets: [ { label: "CPU", fill: true, lineTension: 0.1, backgroundColor: "rgba(75,192,192,0.4)", borderColor: "rgba(75,192,192,1)", borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0.0, borderJoinStyle: 'miter', pointBorderColor: "rgba(75,192,192,1)", pointBackgroundColor: "#fff", pointBorderWidth: 1, pointHoverRadius: 5, pointHoverBackgroundColor: "rgba(75,192,192,1)", pointHoverBorderColor: "rgba(220,220,220,1)", pointHoverBorderWidth: 2, pointRadius: 1, pointHitRadius: 10, data: [] } ] }; var cpuChart = new Chart(document.getElementById("cpuChart"), { type: 'line', data: cpuChartData, options: options }); var memChartData = { labels: [], datasets: [ { label: "Memory", fill: true, lineTension: 0.1, backgroundColor: "rgba(51, 102, 255,0.4)", borderColor: "rgba(51, 102, 255,1)", borderCapStyle: 'butt', borderDash: [], borderDashOffset: 0.0, borderJoinStyle: 'miter', pointBorderColor: "rgba(51, 102, 255,1)", pointBackgroundColor: "#fff", pointBorderWidth: 1, pointHoverRadius: 5, pointHoverBackgroundColor: "rgba(51, 102, 255,1)", pointHoverBorderColor: "rgba(220,220,220,1)", pointHoverBorderWidth: 2, pointRadius: 1, pointHitRadius: 10, data: [] } ] }; var memChart = new Chart(document.getElementById("memChart"), { type: 'line', data: memChartData, options: options }); //格式化時間,將時間戳轉爲hh:mm:ss的格式 function formatTime(time) { var date = new Date(); date.setTime(time); return (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes()) + ":" + (date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds()); } //調用服務端API function callAPI(method, data, callBack) { var params = {}; params.method = method; //請求的API名 params.data = data; //請求參數 $.ajax({ type: "post", //POST方式 url: "http://localhost:3000/api", //API入口 headers: { "Content-Type": "application/json; charset=UTF-8" }, data: JSON.stringify(params), success: function (result, status, xhr) { if (result.code == 1) { //code = 1 說明API調用沒有異常 callBack && callBack(result.data); } else { alert('call api error: [' + result.code + '] ' + result.msg); } }, error: function (XMLHttpRequest, textStatus, errorThrown) { alert('call api error: ' + textStatus); } }); } //更新圖表,刷新數據 function updateChart(chart, chartData, newData) { chartData.datasets[0].data.length = 0; chartData.labels.length = 0; newData.forEach(function (obj) { chartData.labels.push(formatTime(obj[0])); chartData.datasets[0].data.push(parseInt(obj[1])); }); chart.update(); } //每隔10秒調用一次API function update() { callAPI("system.performance", {}, function(data) { updateChart(cpuChart, cpuChartData, data.cpu); updateChart(memChart, memChartData, data.mem); setTimeout(update, 10000); }); } update(); </script> </body> </html>
詳細的路由和頁面開發說明請參考文檔 weroll - Guide: View Router
目前爲止咱們基本已經完成了監控應用的開發,如今讓咱們來從新運行應用,看看效果吧!
最後,咱們還須要實現每日發送郵件給運維人員,報告昨日服務器的監控情況。這時咱們就須要編寫 Daily腳本 來實現這個功能。
一樣,咱們先編寫計劃任務腳本,建立文件 ./server/schedule/report.js,一樣實現 exec 方法:
/* ./server/schedule/report.js */ //使用weroll內置的郵件工具類發送郵件 //你也能夠用其餘郵件庫實現發送 var MailUtil = require('weroll/utils/MailUtil'); exports.exec = function(callBack, option) { var Performance = global.Performance; if (!Performance) return callBack(new Error("invalid Performance data")); var result = { cpu:{}, mem:{} }, total = 0; var yesterdayEnd = new Date(); yesterdayEnd.setHours(0); yesterdayEnd.setMinutes(0); yesterdayEnd.setSeconds(0); yesterdayEnd.setMilliseconds(0); //yesterdayEnd 便是新的一天的開始, 昨日的結束 yesterdayEnd = yesterdayEnd.getTime() - 1; var cpu = Performance.cpu || []; result.cpu.max = cpu[0][11]; cpu.forEach(function(obj) { if (obj[0] > yesterdayEnd) return; //得到昨日CPU使用率峯值和出現時間 result.cpu.max = Math.max(obj[1], result.cpu.max); result.cpu.maxTime = obj[0]; total += obj[1]; }); //得到昨日CPU使用率平均值 result.cpu.avg = total / cpu.length; total = 0; var mem = Performance.mem || []; result.mem.max = mem[0][12]; mem.forEach(function(obj) { if (obj[0] > yesterdayEnd) return; //得到昨日內存使用率峯值和出現時間 result.mem.max = Math.max(obj[1], result.mem.max); result.mem.maxTime = obj[0]; total += obj[1]; }); //得到昨日內存使用率平均值 result.mem.avg = total / mem.length; //撰寫報告, 發送郵件 var title = (option.title || "{DATE} 服務器性能監控報告").replace("{DATE}", moment(yesterdayEnd).format("YYYY-MM-DD")); //定義文本格式的正文 var plain = `CPU 平均使用率: ${Number(result.cpu.avg).toFixed(2)}%\r\n`; plain += `CPU 峯值: ${Number(result.cpu.max).toFixed(2)}% 出現於: ${moment(result.cpu.maxTime).format("HH:mm:ss")}\r\n\r\n`; plain += `內存 平均使用率: ${Number(result.cpu.avg).toFixed(2)}%\r\n`; plain += `內存 峯值: ${Number(result.cpu.max).toFixed(2)}% 出現於: ${moment(result.cpu.maxTime).format("HH:mm:ss")}\r\n\r\n`; plain += "\r\n\r\n" + (option.senderName || ""); //定義HTML格式的正文 var html = plain.replace(/\r\n/g, '<br>'); var content = { plain:plain, html:html }; MailUtil.send(option.sendTo, title, content, function(err) { //結束 callBack(); }); }
而後配置計劃任務的執行規則,在 ./server/config/localdev/schedule.json 裏的 list 數組添加:
{ "type":2, "time":"00:00:10", "script":"report", "option":{ "sendTo":"admin@xxx.com", "senderName":"Robot" } }
以上規則定義了 report.js 腳本將在天天00:00:10的時候執行一次,將郵件發送給admin@xxx.com。
最後,咱們須要初始化郵件發送工具類 MailUtil:
/* ./main.js */ //... app.addTask(function(cb) { //初始化郵件服務 var config = { smtp:{ user:"developer@magicfish.cn", password:"xxxxxxxxx", host:"smtp.xxxx.com", port:465, ssl:true }, sender:"developer@magicfish.cn", senderName:"Robot" }; require("weroll/utils/MailUtil").init(config); //啓動計劃任務管理器 require("weroll/schedule/ScheduleManager").start(); cb(); }); //...
實際收到郵件的效果: