1. 程式人生 > >CAT研究之使用控制檯進行cat-home端排錯

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繼承鏈

從以上的繼承鏈來看,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條記錄)
圖1

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
Transaction - ReloadTask

3.3 同一package下的其他類

3.3.1 HttpConnector

CAT正是使用此類來進行get/post請求,進而驗證連通性。

3.3.2 ThirdPartyConfigManager

負責從資料庫,或者預設的配置檔案中讀取thirdParty相關的配置資訊。

// 資料庫存放配置資訊對應的name欄位值
private static final String CONFIG_NAME = "thirdPartyConfig";

資料庫截圖
cat資料庫

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;
    }
}

言語過於抽象,遂貼上幾幅圖以方便理解。
Event-Channel
全部失敗了

由以上兩幅截圖,我們可以確定警告已經如期產生,只是在最終的傳送階段除了差池。而在定位了以上問題之後,筆者很快就找到了問題的根源。輕鬆地排除了錯誤。

4. 補充

  1. CAT-Home的控制檯不失為一個快速瞭解CAT服務端的捷徑。我們可以通過使用IDE的查詢功能快速定位感興趣的細節。例如上面的“Channel:mail” Event, 我們就可以使用如下方式快速定位。注意這裡的查詢內容中,把前面的 " 帶上可以大大縮小查詢範圍。
    快速定位

  2. 本文所討論的相關類所在的com.dianping.cat.report.alert package 。 旗下的子package就分別屬於一種型別告警。
    這裡寫圖片描述