CAT研究之使用控制檯進行cat-home端排錯
本文將以解決一個ping告警失敗的問題為切入點,介紹如何利用CAT-Home端的日誌來簡化和加深CAT-HOME端的原始碼理解。
1. 概述
最近在測試Cat的告警功能時,驚訝地發現之前已經測試通過的ping告警功能居然無法成功重現了,這就很尷尬了!對於目前的我而言,CAT整體就是一個大黑盒。不出現明顯的錯誤,愣是有種無從下手的感覺。
2. 思路
既然發現了問題,即使感覺無從下手,也必須強迫自己去面對。畢竟不能指望問題會自己把自己解決掉。
回想一下Cat-Home的設計思路,以及平時在進行Cat控制檯操作時的所見所聞,大致可以作出如下判斷:既然CAT-HOME作為Server執行時,同時也是作為一個Client不斷向服務端上報自身的執行情況,那麼應該可以在控制檯上縮小問題的規模,進而最終定位和解決問題
3. 探究
正如本文開頭部分提到的,這次我們就以ping告警失敗作為切入點來進行探究。
從官方文件告警文件中得到的提示ping告警是一種HeartBeat檢測
,我們最終找到了cat-home
專案中的ThirdPartyAlert
類。
3.1 ThirdPartyAlert
類
在經過一番確認之後,我們基本可以認定就是這個類實現了ping告警的功能。接下來我們就對這個類的實現細節進行一些探究。
從以上的繼承鏈來看,ThirdPartyAlert
的實現還是相當簡單的(這也符合CAT一直所秉承的開發原則——”簡單的架構就是最好的架構”)。我們需要關注的重點應該只有其實現的java.lang.Runnable
// ThirdPartyAlert實現的java.lang.Runnable介面
@Override
public void run() {
// 啟動時,保證執行緒休眠到下一個分鐘的開始處, 才開始執行; 休眠的刻度以毫秒為單位
boolean active = TimeHelper.sleepToNextMinute();
while (active) {
// 每分鐘建立一個type為AlertThirdParty的 Transaction; 參見下方的圖1
Transaction t = Cat.newTransaction("AlertThirdParty" , TimeHelper.getMinuteStr());
// 開始執行前的時刻
long current = System.currentTimeMillis();
try {
List<ThirdPartyAlertEntity> alertEntities = new ArrayList<ThirdPartyAlertEntity>();
// m_entities中的Item最終來源屬於另外一個類ThirdPartyAlertBuilder中的邏輯; 具體是 ThirdPartyAlertBuilder.HttpReconnector
// 對於ping告警,一般是2次ping不通或者超時則告警; 具體原因正是這裡, 首次ping失敗會直接進行HttpReconnector的邏輯;如果依然ping不通,就會正式進入告警環節。
while (m_entities.size() > 0) {
ThirdPartyAlertEntity entity = m_entities.poll(5, TimeUnit.MILLISECONDS);
alertEntities.add(entity);
}
// 按照上面的講解, 程式碼邏輯執行到這裡時, alertEntities欄位中存放的正是本次需要告警的專案
// 按Domain對ThirdParty Alert進行分組
Map<String, List<ThirdPartyAlertEntity>> domain2AlertMap = buildDomain2AlertMap(alertEntities);
for (Entry<String, List<ThirdPartyAlertEntity>> entry : domain2AlertMap.entrySet()) {
String domain = entry.getKey();
List<ThirdPartyAlertEntity> thirdPartyAlerts = entry.getValue();
// 按Domain構建AlertEntity
AlertEntity entity = new AlertEntity();
entity.setDate(new Date()).setContent(thirdPartyAlerts.toString()).setLevel(AlertLevel.WARNING);
entity.setMetric(getName()).setType(getName()).setGroup(domain);
// 將本次需要告警的專案封裝為統一的AlertEntity例項, 交由AlertManager進行統一的通知(sms/email/weixin)
m_sendManager.addAlert(entity);
}
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
Cat.logError(e);
} finally {
t.complete();
}
// 計算執行任務的耗時
long duration = System.currentTimeMillis() - current;
try {
// 如果耗時小於一分鐘, 則休眠滿一分鐘; 否則直接進行下次排程
// DURATION 的值為 一分鐘的毫秒數 - 60 * 1000L
if (duration < DURATION) {
Thread.sleep(DURATION - duration);
}
} catch (InterruptedException e) {
active = false;
}
}
}
圖1(注意圖中的45代表當前已經是一個小時裡的第45分鐘,所以該類Transaction已經累計了45條記錄)
3.2 ThirdPartyAlertBuilder
類
前面我們提到,本次需要進行告警的專案正是有此類產生的。而通過觀察其繼承鏈我們可以看到,關鍵性的方法依然是其實現的介面java.lang.Runnable
:
@Override
public void run() {
boolean active = true;
while (active) {
// 本次迴圈開始的時刻
long current = System.currentTimeMillis();
// 一個專門的Transaction; 參見下方的圖2
Transaction t = Cat.newTransaction("ReloadTask", "AlertThirdPartyBuilder");
try {
// 此方法的邏輯就是,對於配置的請求地址,首次請求無正常響應, 則馬上另起執行緒進行重連
// 對於重連依然沒有響應的, 則將其新增到ThirdPartyAlert例項中; 這樣就和上面的邏輯契合了。
buildAlertEntities(current);
t.setStatus(Transaction.SUCCESS);
} catch (Exception e) {
t.setStatus(e);
m_logger.error(e.getMessage(), e);
} finally {
t.complete();
}
long duration = System.currentTimeMillis() - current;
try {
if (duration < DURATION) {
Thread.sleep(DURATION - duration);
}
} catch (InterruptedException e) {
active = false;
}
}
}
圖2
3.3 同一package下的其他類
3.3.1 HttpConnector
類
CAT正是使用此類來進行get/post請求,進而驗證連通性。
3.3.2 ThirdPartyConfigManager
類
負責從資料庫,或者預設的配置檔案中讀取thirdParty相關的配置資訊。
// 資料庫存放配置資訊對應的name欄位值
private static final String CONFIG_NAME = "thirdPartyConfig";
資料庫截圖
3.4 AlertManager
類
本類對於CAT-Home有著關鍵性的作用,告警的傳送正是在此類中完成排程,
此類在例項化時,藉助IOC容器即在後臺進行著兩個傳送告警通知的任務。
// 實現的Initializable介面方法
@Override
public void initialize() throws InitializationException {
// SendExecutor在其實現中回撥AlertManager.send, 傳送告警資訊
Threads.forGroup("cat").s在111tart(new SendExecutor());
// RecoveryAnnouncer在其實現中回撥AlertManager.sendRecoveryMessage, 傳送恢復告警資訊
Threads.forGroup("cat").start(new RecoveryAnnouncer());
}
我們還可以在本類中找到所接收到的恢復告警郵件等的內容模板。
3.5 SenderManager
類
最終來看,AlertManager
也是一個排程者,而將具體的告警傳送的實現方式排程給了SenderManager
。
public boolean sendAlert(AlertChannel channel, AlertMessageEntity message) {
String channelName = channel.getName();
try {
boolean result = true;
String str = "nosend";
// 如果本服務端負責傳送告警
if (m_configManager.isSendMachine()) {
// 觀察Sender的繼承鏈, 就能看到mail, sms, weixin三種實現方式
Sender sender = m_senders.get(channelName);
result = sender.send(message);
// 欄位 str 應該反映的是 傳送情況,即是否傳送成功
str = String.valueOf(result);
}
// 以後如果發現告警失敗,我們就可以關注這個Event, 檢視傳送是否成功
// 如果這個存在, 則表明警告已產生, 只是傳送告警失敗; 反之若不存在, 則代表告警都沒有正常生成。
Cat.logEvent("Channel:" + channelName, message.getType() + ":" + str, Event.SUCCESS, null);
return result;
} catch (Exception e) {
Cat.logError(e);
return false;
}
}
言語過於抽象,遂貼上幾幅圖以方便理解。
由以上兩幅截圖,我們可以確定警告已經如期產生,只是在最終的傳送階段除了差池。而在定位了以上問題之後,筆者很快就找到了問題的根源。輕鬆地排除了錯誤。
4. 補充
CAT-Home的控制檯不失為一個快速瞭解CAT服務端的捷徑。我們可以通過使用IDE的查詢功能快速定位感興趣的細節。例如上面的“Channel:mail” Event, 我們就可以使用如下方式快速定位。注意這裡的查詢內容中,把前面的
"
帶上可以大大縮小查詢範圍。
本文所討論的相關類所在的
com.dianping.cat.report.alert
package 。 旗下的子package就分別屬於一種型別告警。