深入剖析分布式监控 CAT —— 消息文件存储
項(xiàng)目簡介
CAT(Central Application Tracking),是基于 Java 開發(fā)的分布式實(shí)時監(jiān)控系統(tǒng)。CAT 目前在美團(tuán)點(diǎn)評的產(chǎn)品定位是應(yīng)用層的統(tǒng)一監(jiān)控組件,在中間件(RPC、數(shù)據(jù)庫、緩存、MQ 等)框架中得到廣泛應(yīng)用,為各業(yè)務(wù)線提供系統(tǒng)的性能指標(biāo)、健康狀況、實(shí)時告警等。
CAT 目前在美團(tuán)點(diǎn)評已經(jīng)基本覆蓋全部業(yè)務(wù)線,每天處理的消息總量 3200 億+,存儲消息量近 400TB,在通信、計(jì)算、存儲方面都遇到了很大的挑戰(zhàn)。
感興趣的朋友歡迎 Star 開源項(xiàng)目?https://github.com/dianping/cat。
消息模型
組織關(guān)系
消息模型UML圖
消息類型
| Transaction | 記錄一段代碼的執(zhí)行時間和次數(shù) | 1. 執(zhí)行時間較長的業(yè)務(wù)邏輯監(jiān)控。 2. 記錄完整調(diào)用過程。 |
| Event | 記錄一段代碼的執(zhí)行次數(shù)或事件是否發(fā)生 | 統(tǒng)計(jì)計(jì)數(shù)或異常事件記錄 |
| Metric | 記錄一個業(yè)務(wù)指標(biāo)的變化趨勢 | 業(yè)務(wù)指標(biāo)的發(fā)生次數(shù)、平均值、總和,例如商品訂單。 |
| Heartbeat | 定期上報(bào)數(shù)據(jù)或執(zhí)行某些任務(wù) | 定期上報(bào)統(tǒng)計(jì)信息,如 CPU 利用率、內(nèi)存利用率、連接池狀態(tài)等。 |
埋點(diǎn)示例
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?durationreturn?null; }private?void?doRpcCall()?{throw?new?RuntimeException("rpc?call?timeout");?//?mock?rpc?timeout }LogView 消息樹
LogView 不僅可以分析核心流程的性能耗時,而且可以幫助用戶快速排查和定位問題。例如上述埋點(diǎn)示例對應(yīng)的 LogView:
-
Transation 消息是可嵌套的。
-
logError 可記錄異常堆棧,是一種特殊的 Event 消息。
分布式調(diào)用鏈路
分布式logview示例CAT 可以提供簡單的分布式鏈路功能,典型的場景就是 RPC 調(diào)用。例如客戶端 A 調(diào)用服務(wù)端 B,客戶端 A 會生成 2 個 MessageID:表示客戶端 A 調(diào)用過程的 MessageID-1 和表示服務(wù)端 B 執(zhí)行過程的 MessageID-2,MessageID-2 在客戶端 A 發(fā)起調(diào)用的時候傳遞給服務(wù)端 B,MessageID-2 是 MessageID-1 的兒子節(jié)點(diǎn)。
消息流水線
消息流水線如上圖所示,實(shí)時報(bào)表分析是整個監(jiān)控系統(tǒng)的核心,CAT 服務(wù)端接收客戶端上報(bào)的原始數(shù)據(jù),分發(fā)到不同類型的 Analyzer 線程中,每種類型的任務(wù)由一組 Analyzer 線程構(gòu)成。由于原始消息的數(shù)量龐大,所以需要對數(shù)據(jù)進(jìn)行加工、統(tǒng)計(jì)后生成豐富的報(bào)表,滿足業(yè)務(wù)方排查問題以及性能分析的需求。
其中 Logview 的 Analyzer 線程是本文討論的重點(diǎn),它會收集全量的原始消息,并實(shí)時寫入磁盤,類似實(shí)現(xiàn)一個高吞吐量的簡易版消息系統(tǒng)。此外需要具備一定限度的隨機(jī)讀能力,方便業(yè)務(wù)方定位問題發(fā)生時的“案發(fā)現(xiàn)場”。
對于歷史的 Logview 文件會異步上傳至 HDFS。
消息文件存儲
CAT 針對消息寫多讀少的場景,設(shè)計(jì)并實(shí)現(xiàn)了一套文件存儲。以小時為單位進(jìn)行集中式存儲,每個小時對應(yīng)一個存儲目錄,存儲文件分為索引文件和數(shù)據(jù)文件。用戶可以根據(jù) MessageID 快讀定位到某一個消息。
消息 ID 設(shè)計(jì)
CAT 客戶端會為每個消息樹都會分配唯一的 MessageID,MessageID 總共分為四段,示例格式:shop.service-0a010101-431699-1000。
-
第一段是應(yīng)用名shop.service。
-
第二段是客戶端機(jī)器 IP 的16進(jìn)制碼,0a010101 表示10.1.1.1。
-
第三段是系統(tǒng)當(dāng)前時間除以小時得到的整點(diǎn)數(shù),431699 代表 2019-04-01 19:00:00。
-
第四段是客戶端機(jī)器當(dāng)前小時消息的連續(xù)遞增號。(存儲設(shè)計(jì)的重要依據(jù)點(diǎn))
文件存儲 V1.0
總體概貌
V1.0 版本的文件存儲設(shè)計(jì)比較簡單粗暴,每個客戶端 IP 節(jié)點(diǎn)對應(yīng)分別對應(yīng)一個索引文件和數(shù)據(jù)文件。
消息存儲V1.0總體概貌單個 IP 視角
單個IP視角-
每個索引內(nèi)容由存儲塊首地址和塊內(nèi)偏移地址組成,共 6byte。
-
每個索引內(nèi)容的序號與消息序列號一一對應(yīng),因?yàn)橄⑿蛄刑柺沁B續(xù)遞增號,所以索引文件基本可以保證是順序?qū)憽?/p>
-
為了減少磁盤IO交互和寫入時間,消息采用批量 Gzip 壓縮后順序 append 至數(shù)據(jù)文件。
優(yōu)缺點(diǎn)分析
| 1. 簡單易懂,實(shí)現(xiàn)復(fù)雜度不高。 2. 根據(jù)消息序列號可快速定位索引。 | 1. 隨著業(yè)務(wù)規(guī)模不斷擴(kuò)展,存儲文件的數(shù)量并不可控。 2. 數(shù)據(jù)文件節(jié)點(diǎn)過多導(dǎo)致隨機(jī) IO 惡化,機(jī)器 Load 飆高。 |
文件存儲 V2.0
V2.0 文件存儲進(jìn)行了重新設(shè)計(jì),以解決 V2.0 數(shù)據(jù)文件節(jié)點(diǎn)過多以及隨機(jī) IO 惡化的問題。
總體概貌
消息存儲V2.0總體概貌V2.0 核心設(shè)計(jì)思想:
-
合并同一個應(yīng)用的所有 IP 節(jié)點(diǎn)。
-
引入多級索引,建立 IP、Index、DataOffset 的映射關(guān)系。
-
同一個 IP 的索引數(shù)據(jù)盡可能保證順序存儲。
單個索引文件視角
單個索引文件視角索引文件存儲的特點(diǎn):
-
需要根據(jù) IP + Index 建立一級索引。
-
不同 IP 節(jié)點(diǎn)跳躍式存儲,每次劃分一段連續(xù)且固定大小的存儲空間。
-
同一個 IP 節(jié)點(diǎn)根據(jù) Index 在每塊固定大小的存儲空間內(nèi)順序存儲。
最小索引單元視角
最小索引單元視角上圖是索引結(jié)構(gòu)的最小單元,每個索引文件由若干個最小單元組成。每個單元分為 4 * 1024 個 Segment,第一個 Segment 作為我們的一級索引 Header,存儲 IP、消息序列號與 Segment 的映射信息。剩余 4 * 1024 - 1 個 Segment 作為二級索引,存儲消息的地址。一級索引和二級索引都采用 8byte 存儲每個索引數(shù)據(jù)。
一級索引 Header
-
一級索引共由 4096 個 8byte 構(gòu)成。
-
每個索引數(shù)據(jù)由 64 位存儲,前 32 位為 IP,后 32 位為 baseIndex。
baseIndex = index / 4096,index 為消息遞增序列號。
二級索引
-
二級索引共由 4095 個 segment 構(gòu)成,每個 segment 由 4096 個 8byte 構(gòu)成。
-
每個索引數(shù)據(jù)由 64 位存儲,前 40 位為存儲塊的首地址,后 24 位為解壓后的塊內(nèi)偏移地址。
一級索引 Header 與二級索引關(guān)系
-
一級索引第一個 8byte 存儲可存儲魔數(shù)(圖中用 -1 表示),用于標(biāo)識文件有效性。
-
一級索引剩余 4095 個 8byte 分別與二級索引中每個 segment 順序一一對應(yīng)。
如何定位一個消息
根據(jù)應(yīng)用名定位對應(yīng)的索引文件和數(shù)據(jù)文件。
加載索引文件中的所有一級索引,建立 IP、baseIndex、segmentIndex 的映射表。
從整個索引文件角度看,segmentIndex 是遞增的,1 ~ 4095、4097 ~ 8291,以此類推。
根據(jù)消息序列號 index 計(jì)算得出 baseIndex。
通過 IP、baseIndex 查找映射表,定位 segmentIndex。
計(jì)算消息所對應(yīng)segment的偏移地址:segmentOffset = (index % 4096) * 8,獲得索引數(shù)據(jù)。
根據(jù)索引數(shù)據(jù)中塊偏移地址讀取壓縮的數(shù)據(jù)塊,Snappy 解壓后根據(jù)塊內(nèi)偏移地址讀取消息的二進(jìn)制數(shù)據(jù)。
總結(jié)
針對類似消息系統(tǒng)的數(shù)據(jù)存儲,索引設(shè)計(jì)是比較重要的一環(huán),方案并不是唯一的,需要不斷推敲和完善。文件存儲常用的一些性能優(yōu)化手段:
-
批量、順序?qū)?#xff0c;減少磁盤交互次數(shù)。
-
4K 對齊寫入。
-
數(shù)據(jù)壓縮,常用的壓縮算法有 Gzip、Snappy、LZ4。
-
對象池,避免內(nèi)存頻繁分配。
實(shí)踐出真知,推薦大家學(xué)習(xí)下 Kafka 以及 RocketMQ 源碼,例如 RocketMQ 中單個文件混合存儲的方式、類似 HashMap 結(jié)構(gòu)的 Index 文件設(shè)計(jì)以及內(nèi)存映射等都是比較好的學(xué)習(xí)資源。
總結(jié)
以上是生活随笔為你收集整理的深入剖析分布式监控 CAT —— 消息文件存储的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019年DevOps实践最有价值的技能
- 下一篇: 图解分布式架构的演进过程