深刻剖析分佈式監控 CAT —— 消息文件存儲

項目簡介

CAT(Central Application Tracking),是基於 Java 開發的分佈式實時監控系統。CAT 目前在美團點評的產品定位是應用層的統一監控組件,在中間件(RPC、數據庫、緩存、MQ 等)框架中獲得普遍應用,爲各業務線提供系統的性能指標、健康情況、實時告警等。java

CAT 目前在美團點評已經基本覆蓋所有業務線,天天處理的消息總量 3200 億+,存儲消息量近 400TB,在通訊、計算、存儲方面都遇到了很大的挑戰。git

感興趣的朋友歡迎 Star 開源項目 github.com/dianping/ca…github

消息模型

組織關係

消息模型UML圖

消息類型

消息類型 職責 適用場景
Transaction 記錄一段代碼的執行時間和次數 1. 執行時間較長的業務邏輯監控。
2. 記錄完整調用過程。
Event 記錄一段代碼的執行次數或事件是否發生 統計計數或異常事件記錄
Metric 記錄一個業務指標的變化趨勢 業務指標的發生次數、平均值、總和,例如商品訂單。
Heartbeat 按期上報數據或執行某些任務 按期上報統計信息,如 CPU 利用率、內存利用率、鏈接池狀態等。

埋點示例

public void shopInfo() {
    Transaction t1 = Cat.newTransaction("URL", "/api/v1/shop");

    try {
        Transaction t2 = Cat.newTransaction("Redis", "getShop");
        String result = getCache();
        t2.complete();

        if (result != null) {
            Cat.logEvent("CacheHit", "Success");
        } else {
            Cat.logEvent("CacheHit", "Fail");
        }

        Transaction t3 = Cat.newTransaction("Rpc", "Call");
        try {
            doRpcCall();
        } catch (Exception e) {
            t3.setStatus(e);
            Cat.logError(e);
        } finally {
            t3.complete();
        }
    } catch (Exception e) {
        t1.setStatus(e);
        Cat.logError(e);
    } finally {
        t1.complete();
    }
}

private String getCache() throws InterruptedException {
    Thread.sleep(10); // mock cache duration
    return null;
}

private void doRpcCall() {
    throw new RuntimeException("rpc call timeout"); // mock rpc timeout
}
複製代碼

LogView 消息樹

LogView 不只能夠分析核心流程的性能耗時,並且能夠幫助用戶快速排查和定位問題。例如上述埋點示例對應的 LogView:算法

  • Transation 消息是可嵌套的。
  • logError 可記錄異常堆棧,是一種特殊的 Event 消息。

logview示例

分佈式調用鏈路

分佈式logview示例

CAT 能夠提供簡單的分佈式鏈路功能,典型的場景就是 RPC 調用。例如客戶端 A 調用服務端 B,客戶端 A 會生成 2 個 MessageID:表示客戶端 A 調用過程的 MessageID-1 和表示服務端 B 執行過程的 MessageID-2,MessageID-2 在客戶端 A 發起調用的時候傳遞給服務端 B,MessageID-2 是 MessageID-1 的兒子節點。數據庫

消息流水線

消息流水線

如上圖所示,實時報表分析是整個監控系統的核心,CAT 服務端接收客戶端上報的原始數據,分發到不一樣類型的 Analyzer 線程中,每種類型的任務由一組 Analyzer 線程構成。因爲原始消息的數量龐大,因此須要對數據進行加工、統計後生成豐富的報表,知足業務方排查問題以及性能分析的需求。api

其中 Logview 的 Analyzer 線程是本文討論的重點,它會收集全量的原始消息,並實時寫入磁盤,相似實現一個高吞吐量的簡易版消息系統。此外須要具有必定限度的隨機讀能力,方便業務方定位問題發生時的「案發現場」緩存

對於歷史的 Logview 文件會異步上傳至 HDFS。性能優化

消息文件存儲

CAT 針對消息寫多讀少的場景,設計並實現了一套文件存儲。以小時爲單位進行集中式存儲,每一個小時對應一個存儲目錄,存儲文件分爲索引文件和數據文件。用戶能夠根據 MessageID 快讀定位到某一個消息。app

消息 ID 設計

CAT 客戶端會爲每一個消息樹都會分配惟一的 MessageID,MessageID 總共分爲四段,示例格式:shop.service-0a010101-431699-1000。框架

  • 第一段是應用名shop.service。
  • 第二段是客戶端機器 IP 的16進制碼,0a010101 表示10.1.1.1。
  • 第三段是系統當前時間除以小時獲得的整點數,431699 表明 2019-04-01 19:00:00。
  • 第四段是客戶端機器當前小時消息的連續遞增號。(存儲設計的重要依據點

文件存儲 V1.0

整體概貌

V1.0 版本的文件存儲設計比較簡單粗暴,每一個客戶端 IP 節點對應分別對應一個索引文件和數據文件。

消息存儲V1.0整體概貌

單個 IP 視角

單個IP視角

  • 每一個索引內容由存儲塊首地址和塊內偏移地址組成,共 6byte。
  • 每一個索引內容的序號與消息序列號一一對應,由於消息序列號是連續遞增號,因此索引文件基本能夠保證是順序寫。
  • 爲了減小磁盤IO交互和寫入時間,消息採用批量 Gzip 壓縮後順序 append 至數據文件。

優缺點分析

優勢 缺點
1. 簡單易懂,實現複雜度不高。
2. 根據消息序列號可快速定位索引。
1. 隨着業務規模不斷擴展,存儲文件的數量並不可控。
2. 數據文件節點過多致使隨機 IO 惡化,機器 Load 飆高。

文件存儲 V2.0

V2.0 文件存儲進行了從新設計,以解決 V2.0 數據文件節點過多以及隨機 IO 惡化的問題。

整體概貌

消息存儲V2.0整體概貌

V2.0 核心設計思想:

  • 合併同一個應用的全部 IP 節點。
  • 引入多級索引,創建 IP、Index、DataOffset 的映射關係。
  • 同一個 IP 的索引數據儘量保證順序存儲。

單個索引文件視角

單個索引文件視角

索引文件存儲的特色:

  • 須要根據 IP + Index 創建一級索引。
  • 不一樣 IP 節點跳躍式存儲,每次劃分一段連續且固定大小的存儲空間。
  • 同一個 IP 節點根據 Index 在每塊固定大小的存儲空間內順序存儲。

最小索引單元視角

最小索引單元視角

上圖是索引結構的最小單元,每一個索引文件由若干個最小單元組成。每一個單元分爲 4 * 1024 個 Segment,第一個 Segment 做爲咱們的一級索引 Header,存儲 IP、消息序列號與 Segment 的映射信息。剩餘 4 * 1024 - 1 個 Segment 做爲二級索引,存儲消息的地址。一級索引和二級索引都採用 8byte 存儲每一個索引數據。

一級索引 Header

  • 一級索引共由 4096 個 8byte 構成。

  • 每一個索引數據由 64 位存儲,前 32 位爲 IP,後 32 位爲 baseIndex。

    baseIndex = index / 4096,index 爲消息遞增序列號。

二級索引

  • 二級索引共由 4095 個 segment 構成,每一個 segment 由 4096 個 8byte 構成。
  • 每一個索引數據由 64 位存儲,前 40 位爲存儲塊的首地址,後 24 位爲解壓後的塊內偏移地址。

一級索引 Header 與二級索引關係

  • 一級索引第一個 8byte 存儲可存儲魔數(圖中用 -1 表示),用於標識文件有效性。
  • 一級索引剩餘 4095 個 8byte 分別與二級索引中每一個 segment 順序一一對應。

如何定位一個消息

  1. 根據應用名定位對應的索引文件和數據文件。

  2. 加載索引文件中的全部一級索引,創建 IP、baseIndex、segmentIndex 的映射表。

    從整個索引文件角度看,segmentIndex 是遞增的,1 ~ 409五、4097 ~ 8291,以此類推。

  3. 根據消息序列號 index 計算得出 baseIndex。

  4. 經過 IP、baseIndex 查找映射表,定位 segmentIndex。

  5. 計算消息所對應segment的偏移地址:segmentOffset = (index % 4096) * 8,得到索引數據。

  6. 根據索引數據中塊偏移地址讀取壓縮的數據塊,Snappy 解壓後根據塊內偏移地址讀取消息的二進制數據。

總結

針對相似消息系統的數據存儲,索引設計是比較重要的一環,方案並非惟一的,須要不斷推敲和完善。文件存儲經常使用的一些性能優化手段:

  • 批量、順序寫,減小磁盤交互次數。
  • 4K 對齊寫入。
  • 數據壓縮,經常使用的壓縮算法有 Gzip、Snappy、LZ4。
  • 對象池,避免內存頻繁分配。

實踐出真知,推薦你們學習下 Kafka 以及 RocketMQ 源碼,例如 RocketMQ 中單個文件混合存儲的方式、相似 HashMap 結構的 Index 文件設計以及內存映射等都是比較好的學習資源。

轉載請註明出處,歡迎關注個人公衆號:亞普的技術輪子

亞普的技術輪子
相關文章
相關標籤/搜索