前言
首先介紹下背景:我們公司的產品,會直接部署在甲方,因為甲方比較多,且他們包含個性化需求較多,所以,每個甲方可以理解為我們產品的一條git分支。 由於甲方的機器環境、網路環境各不相同,時常出現一些執行時的問題,於是,我設計了這套簡易的智慧監控系統,用來實時監測各個甲方介面情況。
適用範圍
該套方案衍生的適用範圍如下:
- docker下多容器執行專案,且暫不具備介面健康檢測,該套方案可實施檢測多個執行點狀態情況。
- 公司同一套程式碼有多個執行環境,需要監控各個環境狀態。
- 專案分支化後,部署在各個甲方(含個性化需求)------我們是這個。
效果圖
滑鼠移到某個介面,可看某節點產生的時間和當時介面的出參。[這裡只顯示變動節點] 其中200為出參json的一個狀態碼,我們專案普遍採用。如出參無法解析出狀態碼(比如伺服器500錯誤),則不顯示。單擊節點可以再次模擬該次節點,在瀏覽器中顯出出參。
雙擊某個節點可以複製該節點出參。
主體分析
因為我們產品已經完成了前後端分離(前幾篇文章有介紹),所以,我們重點監控介面。
我的方案是:
- 每隔一定時間,比如3分鐘,主動get(或者post)一下http介面(含使用者資訊),獲得介面出參。
- 比對此次出參和前一次該介面出參是否相同,如果不通則標記下。
- 前端展示,每個介面對應每個專案所有變動的節點,並排列一張節點差異圖。
- 這裡,我們監控並不關心介面出參的內容,不對節點出參進行校驗是否合法,因為我們面向所有介面。
對某個介面分析
這裡因為每個公司的各個介面,入參、出參加密方式不通,而且身份認證也是不相同,我們會在獨立寫一個方法來實現加解密,所以,我只介紹未加密、已解密的引數;關於出參下文以{"code":"200","data":null,"message"::"success"}為例,身份認證我以簡單token進行講解本文。
某個獲取新聞列表介面:
http://*.com/v1/news?token=2c789e34dc81d79feba6a005ad63902b
複製程式碼
解密後的出參:
{"code":"200","data":[{"id":"1","title":"這是一條假新聞","url":"http://"},{"id":"2","title":"這是一條假新聞","url":"http://"}],"message"::"success"}
複製程式碼
由介面可知
- GET 方法
- 入參token=2c789e34dc81d79feba6a005ad63902b
- 出參為{"code":"200","data":[],"message"::"success"}
- 其中*.com對於各個環境可能各不相同,比如容器下為10.0.0.1、10.0.0.2多個地址
- 其中token在各個甲方不相同。
新增新聞獲取介面時:
相對url:
v1/news
複製程式碼
請求型別:
get
複製程式碼
body主體:
token={token}
複製程式碼
corn表示式:
0 0/3 * * * ?(每隔3分鐘執行一次)
複製程式碼
新增A環境時:
host(必填)
http://a.com
複製程式碼
引數(list)
token 2c789e34dc81d79feba6a005ad63902b
複製程式碼
新增B環境時:
host(必填)
http://b.com
複製程式碼
引數(list)
token 4297f44b13955235245b2497399d7a93
複製程式碼
繫結介面和環境
- 一個介面可繫結多個環境
- 一個環境可繫結多個介面
- 資料庫中只記錄單向繫結,邏輯上是雙向的。
比如在新聞獲取介面繫結A、B環境,則每隔3分鐘請求一輪介面。
A:
A環境host+相對url=
http://a.com/v1/news
複製程式碼
請求型別:
get
複製程式碼
body主體:
token=2c789e34dc81d79feba6a005ad63902b
複製程式碼
B:
B環境host+相對url=
http://b.com/v1/news
複製程式碼
請求型別:
get
複製程式碼
body主體:
token=4297f44b13955235245b2497399d7a93
複製程式碼
我們框架本身並不會關心有幾個引數,只會遍歷介面中帶佔位符的,把佔位符中欄位替換為環境裡的引數list中變數。 比如一個body為token=2c789e34dc81d79feba6a005ad63902b&type=1&mobile=2
則新增介面時,body填入:
token={token}&type={type}&mobile={mobile} 其中佔位符可以隨便起名字
複製程式碼
配置環境時引數(list)如下:
k | v |
---|---|
token | 2c789e34dc81d79feba6a005ad63902b |
type | 1 |
mobile | 2 |
引數和佔位符裡的對應,和url其他變數不相關。
前端展示
橫軸代表時間,每個變動的節點都是一次出參變更,節點包含,當時的入參,出參,單擊左鍵可以再次模擬請求得出解密後的出參;雙擊可以複製出參。對於不同的狀態碼,我們有不同的顏色,對應自己產品中錯誤碼選擇合適顏色即可,錯誤等級越高,顏色越顯眼。
某個節點右鍵可以檢視歷史解決方案,以及新增新的解決方案,比如解決了這個錯誤程式碼為999,是重啟服務解決的,則,輸入重啟服務,再點新增。 下次其他人就可以看到這個問題怎麼解決的,優先參照做。不同的展現
包含兩種不同的緯度,可以直接點選進入,檢視A環境所有介面的監控情況,或者檢視介面1所有環境的監控情況。
或者:環境編輯介面:
任務編輯介面:
核心技術
既然是週期性監控,自然少不了使用cron表示式,我們又是java專案,所以採用了quartz框架。
quartz核心程式碼
public class QuartzSchedule {
private static SchedulerFactory sf = new StdSchedulerFactory();
private static Scheduler sched;
final static String groupName = "task";
public static void init() throws IOException, SchedulerException {
//查詢所有需要執行的任務和專案列表
// 使用類載入器,載入mybatis的配置檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 構造sqlSession工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
MprojectTaskDao mprojectTaskDao = sqlSession.getMapper(MprojectTaskDao.class);
sched = sf.getScheduler();
//只查詢所有在m_project_include表裡面的任務
List<MprojectTask> mprojectTaskList = mprojectTaskDao.findList();
for (MprojectTask mprojectTask : mprojectTaskList) {
startJob(mprojectTask);
}
}
public static void stopTask(MprojectTask mprojectTask) {
TriggerKey triggerKey = TriggerKey.triggerKey(mprojectTask.getId(), groupName);
try {
sched.pauseTrigger(triggerKey);// 停止觸發器
sched.unscheduleJob(triggerKey);// 移除觸發器
sched.deleteJob(JobKey.jobKey(mprojectTask.getId(), groupName));// 刪除任務
} catch (Exception e) {
e.printStackTrace();
}
}
/*
停止和暫停均可使用
*/
public static void startTask(MprojectTask mprojectTask) {
//無論是否關閉,先關閉任務再開啟。
stopTask(mprojectTask);
startJob(mprojectTask);
}
private static void startJob(MprojectTask mprojectTask) {
try {
JobDetail jobDetail = JobBuilder.newJob(TaskQuzrtzJob.class).withIdentity(mprojectTask.getId(), groupName).build();
// 觸發器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(mprojectTask.getId(), groupName);
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(mprojectTask.getCron()));
// 建立Trigger物件
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
// 排程容器設定JobDetail和Trigger
sched.scheduleJob(jobDetail, trigger);
// 啟動
if (!sched.isShutdown()) {
sched.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
job核心程式碼
public class TaskQuzrtzJob implements Job {
public TaskQuzrtzJob() throws IOException {
}
//JobExecutionContext context傳遞引數值
public void execute(JobExecutionContext context) {
//通過context得到該任務的所有環境
//遍歷所有環境
//拼湊出請求地址
//使用封裝的框架加密傳送請求
//得出出參解密後的結果
//與上次進行比對,不相同則標記,入節點表
//入記錄表
}
}
複製程式碼
資料量
因為我們這個監控每個介面(任務)有多個環境,假設100個介面100個環境,每3分鐘監控一次,則每天記錄量為
100 * 100 * 20 * 24=4800 000條記錄,這個記錄比較驚人,所以我們分了兩張表,一張表只記錄記錄,另外一張表記錄變更,展示節點的時候只查詢變更表,可解決效能上的問題。
前端
前端採用css來畫圓和顏色
round {
border-radius: 50%;
text-align: center;
width: 25px;
height: 25px;
line-height: 25px;
}
.on {
border: 1px solid #7CBA23;
}
複製程式碼
線條使用line畫線
line {
border-bottom: 1px solid gainsboro;
height: 2px;
width: 20px;
}
複製程式碼
整體採用angular js渲染列表
資料庫
我們使用mysql資料庫,mybatis框架連線。
異常提醒
我們會對每個介面和狀態碼進行關聯,異常時,傳送郵件,測試人員核實問題後反饋給開發,開發解決後,錄入解決方案,以便下次查詢。
總結
這個平臺已經試行1個多月了,總體滿足我們的需求,以往,我們跑測試指令碼,往往不能實時監測伺服器健康狀態,現在,任何一個環境有問題,我們都實時,並且可以統計出某個環境從什麼時間到什麼時間介面處於異常。
這套平臺,功能不多,但極大的簡化了我們的對環境穩定性的評估,也收集了大量的資料資訊便於後期統計分析。 其次,該方案稍作改造,可運用於多種監測場景中。
展望
未來,我會融入一些智慧化的分析,比如下文是某次郵件提醒的內容:
(智慧監控) 發現《A客戶私有環境》中 【獲取新聞列表】 介面異常,異常程式碼為102; 出參為{"data":null,"message":"成功","state":102}
歷史解決方案有:
- 到管理端重啟容器。
- xx表中有異常髒資料。 根據歷史統計,方案1可能性為80%,方案2為10%。
該環境介面在5分鐘前是正常的,出參為{"data":null,"message":"暫無資料","state":200}
發現有另外2個其它環境異常,分別為XXX、YYY環境。但異常狀態和該校不相似,排除介面war包bug的原因。
由於《XXX環境》其他介面在5分鐘內某次監控均為異常,所以(智慧監控) 已自主呼叫容器自愈模組,目前已恢復服務,本次收集資料耗時5分鐘,分析問題耗時100毫秒,自愈耗時60秒,該環境中斷服務時間為6分鐘,本月中斷服務總時長為15分鐘,高可用性為98.33%。
over