2021 最牛逼的監控系統!網友:直接收藏……

「來源: |全棧開發者社群 ID:quanzhankaifazhe」

點選上方

[

全棧開發者社群

]

右上角

[...]

[設為

星標

]

2021 最牛逼的監控系統!網友:直接收藏……

點選領取全棧資料:全棧資料

本文來說說什麼是 APM 系統,也就是大家平時說的監控系統,以及怎麼實現一個 APM 系統。因為一些特殊的原因,我在文中會使用 Dog 作為我們的系統名稱進行介紹。

我們為 Dog 規劃的目標是接入公司的大部分應用,預計每秒處理 500MB-1000MB 的資料,單機每秒 100MB 左右,使用多臺普通的 AWS EC2。

因為本文的很多讀者供職的公司不一定有比較全面的 APM 系統,所以我儘量照顧更多讀者的閱讀感受,會在有些內容上囉嗦一些,希望大家可以理解。我會在文中提到 prometheus、grafana、cat、pinpoint、skywalking、zipkin 等一系列工具,如果你沒有用過也不要緊,我會充分考慮到這一點。

本文預設的一些背景:Java 語言、web 服務、每個應用有多個例項、以微服務方式部署。另外,從文章的可閱讀性上考慮,我假設每個應用的不同例項分佈在不同的 IP 上,可能你的應用場景不一定是這樣的。

APM 簡介

APM 通常認為是 Application Performance Management 的簡寫,它主要有三個方面的內容,分別是 Logs(日誌)、Traces(鏈路追蹤) 和 Metrics(報表統計)。以後大家接觸任何一個 APM 系統的時候,都可以從這三個方面去分析它到底是什麼樣的一個系統。

有些場景中,APM 特指上面三個中的 Metrics,我們這裡不去討論這個概念

這節我們先對這 3 個方面進行介紹,同時介紹一下這 3 個領域裡面一些常用的工具。

1、首先 Logs 最好理解,就是對各個應用中列印的 log 進行收集和提供查詢能力。

Logs 系統的重要性不言而喻,通常我們在排查特定的請求的時候,是非常依賴於上下文的日誌的。

以前我們都是透過 terminal 登入到機器裡面去查 log(我好幾年都是這樣過來的),但是由於叢集化和微服務化的原因,繼續使用這種方式工作效率會比較低,因為你可能需要登入好幾臺機器搜尋日誌才能找到需要的資訊,所以需要有一個地方中心化儲存日誌,並且提供日誌查詢。

Logs 的典型實現是 ELK (ElasticSearch、Logstash、Kibana),三個專案都是由 Elastic 開源,其中最核心的就是 ES 的儲存和查詢的效能得到了大家的認可,經受了非常多公司的業務考驗。

Logstash 負責收集日誌,然後解析並存儲到 ES。通常有兩種比較主流的日誌採集方式,一種是透過一個客戶端程式 FileBeat,收集每個應用列印到本地磁碟的日誌,傳送給 Logstash;另一種則是每個應用不需要將日誌儲存到磁碟,而是直接傳送到 Kafka 叢集中,由 Logstash 來消費。

Kibana 是一個非常好用的工具,用於對 ES 的資料進行視覺化,簡單來說,它就是 ES 的客戶端。

2021 最牛逼的監控系統!網友:直接收藏……

我們回過頭來分析 Logs 系統,Logs 系統的資料來自於應用中列印的日誌,它的特點是資料量可能很大,取決於應用開發者怎麼打日誌,Logs 系統需要儲存全量資料,通常都要支援至少 1 周的儲存。

每條日誌包含 ip、thread、class、timestamp、traceId、message 等資訊,它涉及到的技術點非常容易理解,就是日誌的儲存和查詢。

使用也非常簡單,排查問題時,通常先透過關鍵字搜到一條日誌,然後透過它的 traceId 來搜尋整個鏈路的日誌。

題外話,Elastic 其實除了 Logs 以外,也提供了 Metrics 和 Traces 的解決方案,不過目前國內使用者主要是使用它的 Logs 功能。

2、我們再來看看 Traces 系統,它用於記錄整個呼叫鏈路。

前面介紹的 Logs 系統使用的是開發者列印的日誌,所以它是最貼近業務的。而 Traces 系統就離業務更遠一些了,它關注的是一個請求進來以後,經過了哪些應用、哪些方法,分別在各個節點耗費了多少時間,在哪個地方丟擲的異常等,用來快速定位問題。

經過多年的發展,Traces 系統雖然在服務端的設計很多樣,但是客戶端的設計慢慢地趨於統一,所以有了 OpenTracing 專案,我們可以簡單理解為它是一個規範,它定義了一套 API,把客戶端的模型固化下來。當前比較主流的 Traces 系統中,Jaeger、SkyWalking 是使用這個規範的,而 Zipkin、Pinpoint 沒有使用該規範。限於篇幅,本文不對 OpenTracing 展開介紹。

下面這張圖是我畫的一個請求的時序圖:

2021 最牛逼的監控系統!網友:直接收藏……

從上面這個圖中,可以非常方便地看出,這個請求經過了 3 個應用,透過線的長短可以非常容易看出各個節點的耗時情況。通常點選某個節點,我們可以有更多的資訊展示,比如點選 HttpClient 節點我們可能有 request 和 response 的資料。

下面這張圖是 Skywalking 的圖,它的 UI 也是蠻好的:

2021 最牛逼的監控系統!網友:直接收藏……

SkyWalking 在國內應該比較多公司使用,是一個比較優秀的由國人發起的開源專案,已進入 Apache 基金會。

另一個比較好的開源 Traces 系統是由韓國人開源的 Pinpoint,它的打點資料非常豐富,這裡有官方提供的 Live Demo,大家可以去玩一玩。

2021 最牛逼的監控系統!網友:直接收藏……

最近比較火的是由 CNCF(Cloud Native Computing Foundation) 基金會管理的 Jeager:

2021 最牛逼的監控系統!網友:直接收藏……

當然也有很多人使用的是 Zipkin,算是 Traces 系統中開源專案的老前輩了:

2021 最牛逼的監控系統!網友:直接收藏……

上面介紹的是目前比較主流的 Traces 系統,在排查具體問題的時候它們非常有用,透過鏈路分析,很容易就可以看出來這個請求經過了哪些節點、在每個節點的耗時、是否在某個節點執行異常等。

雖然這裡介紹的幾個 Traces 系統的 UI 不一樣,大家可能有所偏好,但是具體說起來,表達的都是一個東西,那就是一顆呼叫樹,所以我們要來說說每個專案除了 UI 以外不一樣的地方。

首先肯定是資料的豐富度,你往上拉看 Pinpoint 的樹,你會發現它的埋點非常豐富,真的實現了一個請求經過哪些方法一目瞭然。

但是這真的是一個好事嗎?值得大家去思考一下。兩個方面,一個是對客戶端的效能影響,另一個是服務端的壓力。

其次,Traces 系統因為有系統間呼叫的資料,所以很多 Traces 系統會使用這個資料做系統間的呼叫統計,比如下面這個圖其實也蠻有用的:

2021 最牛逼的監控系統!網友:直接收藏……

另外,前面說的是某個請求的完整鏈路分析,那麼就引出另一個問題,我們怎麼獲取這個“某個請求”,這也是每個 Traces 系統的不同之處。

比如上圖,它是 Pinpoint 的圖,我們看到前面兩個節點的圓圈是不完美的,點選前面這個圓圈,就可以看出來原因了:

2021 最牛逼的監控系統!網友:直接收藏……

圖中右邊的兩個紅圈是我加的。我們可以看到在 Shopping-api 呼叫 Shopping-order 的請求中,有 1 個失敗的請求,我們用滑鼠在散點圖中把這個紅點框出來,就可以進入到 trace 檢視,檢視具體的呼叫鏈路了。限於篇幅,我這裡就不去演示其他 Traces 系統的入口了。

還是看上面這個圖,我們看右下角的兩個統計圖,我們可以看出來在最近 5 分鐘內 Shopping-api 呼叫 Shopping-order 的所有請求的耗時情況,以及時間分佈。在發生異常的情況,比如流量突發,這些圖的作用就出來了。

對於 Traces 系統來說,最有用的就是這些東西了,當然大家在使用過程中,可能也發現了 Traces 系統有很多的統計功能或者機器健康情況的監控,這些是每個 Traces 系統的差異化功能,我們就不去具體分析了。

3、最後,我們再來討論 Metrics,它側重於各種報表資料的收集和展示。

在 Metrics 方面做得比較好的開源系統,是大眾點評開源的 Cat,下面這個圖是 Cat 中的 transaction 檢視,它展示了很多的我們經常需要關心的統計資料:

2021 最牛逼的監控系統!網友:直接收藏……

下圖是 Cat 的 problem 檢視,對我們開發者來說就太有用了,應用開發者的目標就是讓這個檢視中的資料越少越好。

2021 最牛逼的監控系統!網友:直接收藏……

本文之後的內容主要都是圍繞著 Metrics 展開的,所以這裡就不再展開更多的內容了。

另外,說到 APM 或系統監控,就不得不提 Prometheus+Grafana 這對組合,它們對機器健康情況、URL 訪問統計、QPS、P90、P99 等等這些需求,支援得非常好,它們用來做監控大屏是非常合適的,但是通常不能幫助我們排查問題,它看到的是系統壓力高了、系統不行了,但不能一下子看出來為啥高了、為啥不行了。

科普:Prometheus 是一個使用記憶體進行儲存和計算的服務,每個機器/應用透過 Prometheus 的介面上報資料,它的特點是快,但是機器宕機或重啟會丟失所有資料。

Grafana 是一個好玩的東西,它透過各種外掛來視覺化各種系統資料,比如查詢 Prometheus、ElasticSearch、ClickHouse、MySQL 等等,它的特點就是酷炫,用來做監控大屏再好不過了。

Metrics 和 Traces

因為本文之後要介紹的我們開發的 Dog 系統從分類來說,側重於 Metrics,同時我們也提供 tracing 功能,所以這裡單獨寫一小節,分析一下 Metrics 和 Traces 系統之間的聯絡和區別。

使用上的區別很好理解,Metrics 做的是資料統計,比如某個 URL 或 DB 訪問被請求多少次,P90 是多少毫秒,錯誤數是多少等這種問題。而 Traces 是用來分析某次請求,它經過了哪些鏈路,比如進入 A 應用後,呼叫了哪些方法,之後可能又請求了 B 應用,在 B 應用裡面又呼叫了哪些方法,或者整個鏈路在哪個地方出錯等這些問題。

不過在前面介紹 Traces 的時候,我們也發現這類系統也會做很多的統計工作,它也覆蓋了很多的 Metrics 的內容。

所以大家先要有個概念,Metrics 和 Traces 之間的聯絡是非常緊密的,它們的資料結構都是一顆呼叫樹,區別在於這顆樹的枝幹和葉子多不多。在 Traces 系統中,一個請求所經過的鏈路資料是非常全的,這樣對排查問題的時候非常有用,但是如果要對 Traces 中的所有節點的資料做報表統計,將會非常地耗費資源,價效比太低。而 Metrics 系統就是面向資料統計而生的,所以樹上的每個節點我們都會進行統計,所以這棵樹不能太“茂盛”。

我們關心的其實是,哪些資料值得統計?首先是入口,其次是耗時比較大的地方,比如 db 訪問、http 請求、redis 請求、跨服務呼叫等。當我們有了這些關鍵節點的統計資料以後,對於系統的健康監控就非常容易了。

我這裡不再具體去介紹他們的區別,大家看完本文介紹的 Metrics 系統實現以後,再回來思考這個問題會比較好。

Dog 在設計上,主要是做一個 Metrics 系統,統計關鍵節點的資料,另外也提供 trace 的能力,不過因為我們的樹不是很”茂盛“,所以鏈路上可能是斷斷續續的,中間會有很多缺失的地帶,當然應用開發者也可以加入手動埋點來彌補。

Dog 因為是公司內部的監控系統,所以對於公司內部大家會使用到的中介軟體相對是比較確定的,不需要像開源的 APM 一樣需要打很多點,我們主要實現了以下節點的自動打點:

http 入口:透過實現一個 Filter 來攔截所有的請求

MySQL: 透過 Mybatis Interceptor 的方式

Redis: 透過 javassist 增強 RedisTemplate 的方式

跨應用呼叫: 透過代理 feign client 的方式,dubbo、grpc 等方式可能需要透過攔截器

http 呼叫: 透過 javassist 為 HttpClient 和 OkHttp 增加 interceptor 的方式

Log 打點: 透過 plugin 的方式,將 log 中列印的 error 上報上來

打點的技術細節,就不在這裡展開了,主要還是用了各個框架提供的一些介面,另外就是用到了 javassist 做位元組碼增強。

這些打點資料就是我們需要做統計的,當然因為打點有限,我們的 tracing 功能相對於專業的 Traces 系統來說單薄了很多。

Dog 簡介

下面是 DOG 的架構圖,客戶端將訊息投遞給 Kafka,由 dog-server 來消費訊息,儲存用到了 Cassandra 和 ClickHouse,後面再介紹具體存哪些資料。

2021 最牛逼的監控系統!網友:直接收藏……

1、也有 APM 系統是不透過訊息中介軟體的,比如 Cat 就是客戶端透過 Netty 連線到服務端來發送訊息的。

2、Server 端使用了 Lambda 架構模式,Dog UI 上查詢的資料,由每一個 Dog-server 的記憶體資料和下游儲存的資料聚合而來。

下面,我們簡單介紹下 Dog UI 上一些比較重要的功能,我們之後再去分析怎麼實現相應的功能。

注意:下面的圖都是我自己畫的,不是真的頁面截圖,數值上可能不太準確

下圖示例 transaction 報表:

2021 最牛逼的監控系統!網友:直接收藏……

點選上圖中 type 中的某一項,我們有這個 type 下面每個 name 的報表。比如點選 URL,我們可以得到每個介面的資料統計:

2021 最牛逼的監控系統!網友:直接收藏……

當然,上圖中點選具體的 name,還有下一個層級 status 的統計資料,這裡就不再貼圖了。Dog 總共設計了 type、name、status 三級屬性。上面兩個圖中的最後一列是 sample,它可以指引到 sample 檢視:

2021 最牛逼的監控系統!網友:直接收藏……

Sample 就是取樣的意思,當我們看到有個介面失敗率很高,或者 P90 很高的時候,你知道出了問題,但因為它只有統計資料,所以你不知道到底哪裡出了問題,這個時候,就需要有一些樣本資料了。我們每分鐘對 type、name、status 的不同組合分別儲存最多 5 個成功、5 個失敗、5 個慢處理的樣本資料。

點選上面的 sample 表中的某個 T、F、L 其實就會進入到我們的 trace 檢視,展示出這個請求的整個鏈路:

2021 最牛逼的監控系統!網友:直接收藏……

透過上面這個 trace 檢視,可以非常快速地知道是哪個環節出了問題。當然,我們之前也說過,我們的 trace 依賴於我們的埋點豐富度,但是 Dog 是一個 Metrics 為主的系統,所以它的 Traces 能力是不夠的,不過大部分情況下,對於排查問題應該是足夠用的。

對於應用開發者來說,下面這個 Problem 檢視應該是非常有用的:

2021 最牛逼的監控系統!網友:直接收藏……

它展示了各種錯誤的資料統計,並且提供了 sample 讓開發者去排查問題。

最後,我們再簡單介紹下 Heartbeat 檢視,它和前面的功能沒什麼關係,就是大量的圖,我們有 gc、heap、os、thread 等各種資料,讓我們可以觀察到系統的健康情況。

2021 最牛逼的監控系統!網友:直接收藏……

這節主要介紹了一個 APM 系統通常包含哪些功能,其實也很簡單對不對,接下來我們從開發者的角度,來聊聊具體的實現細節問題。

客戶端資料模型

大家都是開發者,我就直接一些了,下圖介紹了客戶端的資料模型:

2021 最牛逼的監控系統!網友:直接收藏……

對於一條 Message 來說,用於統計的欄位是 type, name, status,所以我們能基於 type、type+name、type+name+status 三種維度的資料進行統計。

Message 中其他的欄位:timestamp 表示事件發生的時間;success 如果是 false,那麼該事件會在 problem 報表中進行統計;data 不具有統計意義,它只在鏈路追蹤排查問題的時候有用;businessData 用來給業務系統上報業務資料,需要手動打點,之後用來做業務資料分析。

Message 有兩個子類 Event 和 Transaction,區別在於 Transaction 帶有 duration 屬性,用來標識該 transaction 耗時多久,可以用來做 max time, min time, avg time, p90, p95 等,而 event 指的是發生了某件事,只能用來統計發生了多少次,並沒有時間長短的概念。

Transaction 有個屬性 children,可以巢狀 Transaction 或者 Event,最後形成一顆樹狀結構,用來做 trace,我們稍後再介紹。

下面表格示例一下打點資料,這樣比較直觀一些:

2021 最牛逼的監控系統!網友:直接收藏……

簡單介紹幾點內容:

type 為 URL、SQL、Redis、FeignClient、HttpClient 等這些資料,屬於自動埋點的範疇。通常做 APM 系統的,都要完成一些自動埋點的工作,這樣應用開發者不需要做任何的埋點工作,就能看到很多有用的資料。像最後兩行的 type=Order 屬於手動埋點的資料。

打點需要特別注意 type、name、status 的維度“爆炸”,它們的組合太多會非常消耗資源,它可能會直接拖垮我們的 Dog 系統。type 的維度可能不會太多,但是我們可能需要注意開發者可能會濫用 name 和 status,所以我們一定要做 normalize(如 url 可能是帶動態引數的,需要格式化處理一下)。

表格中的最後兩條是開發者手動埋點的資料,通常用來統計特定的場景,比如我想知道某個方法被呼叫的情況,呼叫次數、耗時、是否拋異常、入參、返回值等。因為自動埋點是業務不想關的,冷冰冰的資料,開發者可能想要埋一些自己想要統計的資料。

開發者在手動埋點的時候,還可以上報更多的業務相關的資料上來,參考表格最後一列,這些資料可以做業務分析來用。比如我是做支付系統的,通常一筆支付訂單會涉及到非常多的步驟(國外的支付和大家平時使用的微信、支付寶稍微有點不一樣),透過上報每一個節點的資料,最後我就可以在 Dog 上使用 bizId 來將整個鏈路串起來,在排查問題的時候是非常有用的(我們在做支付業務的時候,支付的成功率並沒有大家想象的那麼高,很多節點可能出問題)。

客戶端設計

上一節我們介紹了單條 message 的資料,這節我們覆蓋一下其他內容。

首先,我們介紹客戶端的 API 使用:

publicvoidtest(){

Transaction transaction = Dog。newTransaction(“URL”, “/test/user”);

try {

Dog。logEvent(“User”, “name-xxx”, “status-yyy”);

// do something

Transaction sql = Dog。newTransaction(“SQL”, “UserMapper。insert”);

// try-catch-finally

transaction。setStatus(“xxxx”);

transaction。setSuccess(true/false);

} catch (Throwable throwable) {

transaction。setSuccess(false);

transaction。setData(Throwables。getStackTraceAsString(throwable));

throw throwable;

} finally {

transaction。finish();

}

}

上面的程式碼示例瞭如何巢狀使用 Transaction 和 Event,當最外層的 Transaction 在 finally 程式碼塊呼叫 finish() 的時候,完成了一棵樹的建立,進行訊息投遞。

我們往 Kafka 中投遞的並不是一個 Message 例項,因為一次請求會產生很多的 Message 例項,而是應該組織成 一個 Tree 例項以後進行投遞。下圖描述 Tree 的各個屬性:

2021 最牛逼的監控系統!網友:直接收藏……

Tree 的屬性很好理解,它持有 root transaction 的引用,用來遍歷整顆樹。另外就是需要攜帶機器資訊 messageEnv。

treeId 應該有個演算法能保證全域性唯一,簡單介紹下 Dog 的實現:{encode(ip)}-當前分鐘{自增id}。

下面簡單介紹幾個 tree id 相關的內容,假設一個請求從 A->B->C->D 經過 4 個應用,A 是入口應用,那麼會有:

1、總共會有 4 個 Tree 物件例項從 4 個應用投遞到 Kafka,跨應用呼叫的時候需要傳遞 treeId, parentTreeId, rootTreeId 三個引數;

2、A 應用的 treeId 是所有節點的 rootTreeId;

3、B 應用的 parentTreeId 是 A 的 treeId,同理 C 的 parentTreeId 是 B 應用的 treeId;

4、在跨應用呼叫的時候,比如從 A 呼叫 B 的時候,為了知道 A 的下一個節點是什麼,所以在 A 中提前為 B 生成 treeId,B 收到請求後,如果發現 A 已經為它生成了 treeId,直接使用該 treeId。

大家應該也很容易知道,透過這幾個 tree id,我們是想要實現 trace 的功能。

介紹完了 tree 的內容,我們再簡單討論下應用整合方案。

整合無外乎兩種技術,一種是透過 javaagent 的方式,在啟動指令碼中,加上相應的 agent,這種方式的優點是開發人員無感知,運維層面就可以做掉,當然開發者如果想要手動做一些埋點,可能需要再提供一個簡單的 client jar 包給開發者,用來橋接到 agent 裡。另一種就是提供一個 jar 包,由開發者來引入這個依賴。

兩種方案各有優缺點,Pinpoint 和 Skywalking 使用的是 javaagent 方案,Zipkin、Jaeger、Cat 使用的是第二種方案,Dog 也使用第二種手動新增依賴的方案。

通常來說,做 Traces 的系統選擇使用 javaagent 方案比較省心,因為這類系統 agent 做完了所有需要的埋點,無需應用開發者感知。

最後,我再簡單介紹一下 Heartbeat 的內容,這部分內容其實最簡單,但是能做出很多花花綠綠的圖表出來,可以實現面向老闆程式設計。

2021 最牛逼的監控系統!網友:直接收藏……

前面我們介紹了 Message 有兩個子類 Event 和 Transaction,這裡我們再加一個子類 Heartbeat,用來上報心跳資料。

我們主要收集了 thread、os、gc、heap、client 執行情況(產生多少個 tree,資料大小,傳送失敗數)等,同時也提供了 api 讓開發者自定義資料進行上報。Dog client 會開啟一個後臺執行緒,每分鐘執行一次 Heartbeat 收集程式,上報資料。

再介紹細一些。核心結構是一個 Map\,key 類似於 “os。systemLoadAverage”, “thread。count”等,字首 os,thread,gc 等其實是用來在頁面上的分類,字尾是顯示的折線圖的名稱。

關於客戶端,這裡就介紹這麼多了,其實實際編碼過程中,還有一些細節需要處理,比如如果一棵樹太大了要怎麼處理,比如沒有 rootTransaction 的情況怎麼處理(開發者只調用了 Dog。logEvent(。。。)),比如內層巢狀的 transaction 沒有呼叫 finish 怎麼處理等等。

Dog server 設計

下圖示例了 server 的整體設計,值得注意的是,我們這裡對執行緒的使用非常地剋制,圖中只有 3 個工作執行緒。

2021 最牛逼的監控系統!網友:直接收藏……

首先是 Kafka Consumer 執行緒,它負責批次消費訊息,從 kafka 叢集中消費到的是一個個 Tree 的例項,接下來考慮怎麼處理它。

在這裡,我們需要將樹狀結構的 message 鋪平,我們把這一步叫做 deflate,並且做一些預處理,形成下面的結構:

2021 最牛逼的監控系統!網友:直接收藏……

接下來,我們就將 DeflateTree 分別投遞到兩個 Disruptor 例項中,我們把 Disruptor 設計成單執行緒生產和單執行緒消費,主要是效能上的考慮。消費執行緒根據 DeflateTree 的屬性使用繫結好的 Processor 進行處理,比如 DeflateTree 中List problmes不為空,同時自己綁定了 ProblemProcessor,那麼就需要呼叫 ProblemProcessor 來處理。

科普時間:Disruptor 是一個高效能的佇列,效能比 JDK 中的 BlockingQueue 要好

這裡我們使用了 2 個 Disruptor 例項,當然也可以考慮使用更多的例項,這樣每個消費執行緒繫結的 processor 就更少。我們這裡把 Processor 繫結到了 Disruptor 例項上,其實原因也很簡單,為了效能考慮,我們想讓每個 processor 只有單執行緒使用它,單執行緒操作可以減少執行緒切換帶來的開銷,可以充分利用到系統快取,以及在設計 processor 的時候,不用考慮併發讀寫的問題。

這裡要考慮負載均衡的情況,有些 processor 是比較耗費 CPU 和記憶體資源的,一定要合理分配,不能把壓力最大的幾個任務分到同一個執行緒中去了。

核心的處理邏輯都在各個 processor 中,它們負責資料計算。接下來,我把各個 processor 需要做的主要內容介紹一下,畢竟能看到這裡的開發者,應該真的是對 APM 的資料處理比較感興趣的。

Transaction processor

transaction processor 是系統壓力最大的地方,它負責報表統計,雖然 Message 有 Transaction 和 Event 兩個主要的子類,但是在實際的一顆樹中,絕大部分的節點都是 transaction 型別的資料。

2021 最牛逼的監控系統!網友:直接收藏……

下圖是 transaction processor 內部的一個主要的資料結構,最外層是一個時間,我們用分鐘時間來組織,我們最後在持久化的時候,也是按照分鐘來存的。第二層的 HostKey 代表哪個應用以及哪個 ip 來的資料,第三層是 type、name、status 的組合。最內層的 Statistics 是我們的資料統計模組。

2021 最牛逼的監控系統!網友:直接收藏……

另外我們也可以看到,這個結構到底會消耗多少記憶體,其實主要取決於我們的 type、name、status 的組合也就是 ReportKey 會不會很多,也就是我們前面在說客戶端打點的時候,要避免維度爆炸。

最外層結構代表的是時間的分鐘表示,我們的報表是基於每分鐘來進行統計的,之後持久化到 ClickHouse 中,但是我們的使用者在看資料的時候,可不是一分鐘一分鐘看的,所以需要做資料聚合,下面展示兩條資料是如何做聚合的,在很多資料的時候,都是按照同樣的方法進行合併。

2021 最牛逼的監控系統!網友:直接收藏……

你仔細想想就會發現,前面幾個資料的計算都沒毛病,但是 P90, P95 和 P99 的計算是不是有點欺騙人啊?其實這個問題是真的無解的,我們只能想一個合適的資料計算規則,然後我們再想想這種計算規則,可能算出來的值也是差不多可用的就好了。

另外有一個細節問題,我們需要讓記憶體中的資料提供最近 30 分鐘的統計資訊,30 分鐘以上的才從 DB 讀取。然後做上面介紹的 merge 操作。

討論:我們是否可以丟棄一部分實時性,我們每分鐘持久化一次,我們讀取的資料都是從 DB 來的,這樣可行嗎?

不行,因為我們的資料是從 kafka 消費來的,本身就有一定的滯後性,我們如果在開始一分鐘的時候就持久化上一分鐘的資料,可能之後還會收到前面時間的訊息,這種情況處理不了。

比如我們要統計最近一小時的情況,那麼就會有 30 分鐘的資料從各個機器中獲得,有 30 分鐘的資料從 DB 獲得,然後做合併。

這裡值得一提的是,在 transaction 報表中,count、failCount、min、max、avg 是比較好算的,但是 P90、P95、P99 其實不太好算,我們需要一個數組結構,來記錄這一分鐘內所有的事件的時間,然後進行計算,我們這裡討巧使用了 Apache DataSketches,它非常好用,這裡我就不展開了,感興趣的同學可以自己去看一下。

到這裡,大家可以去想一想儲存到 ClickHouse 的資料量的問題。app_name、ip、type、name、status 的不同組合,每分鐘一條資料。

Sample Processor

sample processor 消費 deflate tree 中的 List transactions和 List events的資料。

我們也是按照分鐘來取樣,最終每分鐘,對每個 type、name、status 的不同組合,採集最多 5 個成功、5 個失敗、5 個慢處理。

相對來說,這個還是非常簡單的,它的核心結構如下圖:

2021 最牛逼的監控系統!網友:直接收藏……

結合 Sample 的功能來看比較容易理解:

2021 最牛逼的監控系統!網友:直接收藏……

Problem Processor

在做 deflate 的時候,所有 success=false 的 Message,都會被放入 List problmes中,用來做錯誤統計。

Problem 內部的資料結構如下圖:

2021 最牛逼的監控系統!網友:直接收藏……

大家看下這個圖,其實也就知道要做什麼了,我就不囉嗦了。其中 samples 我們每分鐘儲存 5 個 treeId。

順便也再展示下 Problem 的檢視:

2021 最牛逼的監控系統!網友:直接收藏……

關於持久化,我們是存到了 ClickHouse 中,其中 sample 用逗號連線成一個字串,problem_data 的列如下:

event_date, event_time, app_name, ip, type, name, status, count, sample

Heartbeat processor

Heartbeat 處理 List heartbeats 的資料,題外話,正常情況下,一顆樹裡面只有一個 Heartbeat 例項。

前面我也簡單提到了一下,我們 Heartbeat 中用來展示圖表的核心資料結構是一個 Map

收集到的 key-value 資料如下所示:

{

“os。systemLoadAverage”: 1。5,

“os。committedVirtualMemory”: 1234562342,

“os。openFileDescriptorCount”: 800,

“thread。count”: 600,

“thread。httpThreadsCount”: 250,

“gc。ZGC Count”: 234,

“gc。ZGC Time(ms)”: 123435,

“heap。ZHeap”: 4051233219,

“heap。Metaspace”: 280123212

}

字首是分類,字尾是圖的名稱。客戶端每分鐘收集一次資料進行上報,然後就可以做很多的圖了,比如下圖展示了在 heap 分類下的各種圖:

2021 最牛逼的監控系統!網友:直接收藏……

Heartbeat processor 要做的事情很簡單,就是資料儲存,Dog UI 上的資料是直接從 ClickHouse 中讀取的。

heartbeat_data 的列如下:

event_date, event_time, timestamp, app_name, ip, name, value

MessageTree Processor

前面我們多次提到了 Sample 的功能,這些取樣的資料幫助我們恢復現場,這樣我們可以透過 trace 檢視來跟蹤呼叫鏈。

2021 最牛逼的監控系統!網友:直接收藏……

要做上面的這個 trace 檢視,我們需要上下游的所有的 tree 的資料,比如上圖是 3 個 tree 例項的資料。

之前我們在客戶端介紹的時候說過,這幾個 tree 透過 parent treeId 和 root treeId 來組織。

要做這個檢視,給我們提出的挑戰就是,我們需要儲存全量的資料。

大家可以想一想這個問題,為啥要儲存全量資料,我們直接儲存被 sample 到的資料不就好了嗎?

這裡我們用到了 Cassandra 的能力,Cassandra 在這種 kv 的場景中,有非常不錯的效能,而且它的運維成本很低。

我們以 treeId 作為主鍵,另外再加 data 一個列即可,它是整個 tree 的例項資料,資料型別是 blob,我們會先做一次 gzip 壓縮,然後再扔給 Cassandra。

Business Processor

我們在介紹客戶端的時候說過,每個 Message 都可以攜帶 Business Data,不過只有應用開發者自己手動埋點的時候才會有,當我們發現有業務資料的時候,我們會做另一個事情,就是把這個資料儲存到 ClickHouse 中,用來做業務分析。

我們其實不知道應用開發者到底會把它用在什麼場景中,因為每個人負責的專案都不一樣,所以我們只能做一個通用的資料模型。

2021 最牛逼的監控系統!網友:直接收藏……

回過頭來看這個圖,BusinessData 中我們定義了比較通用的 userId 和 bizId,我們認為它們可能是每個業務場景會用到的東西。userId 就不用說了,bizId 大家可以做來記錄訂單 id,支付單 id 等。

然後我們提供了 3 個 String 型別的列 ext1、ext2、ext3 和兩個數值型別的列 extVal1 和 extVal2,它們可以用來表達你的業務相關的引數。

我們的處理當然也非常簡單,將這些資料存到 ClickHouse 中就可以了,表中主要有這些列:

event_data, event_time, user, biz_id, timestamp, type, name, status, app_name、ip、success、ext1、ext2、ext3、ext_val1、ext_val2

這些資料對我們 Dog 系統來說肯定不認識,因為我們也不知道你表達的是什麼業務,type、name、status 是開發者自己定義的,ext1, ext2, ext3 分別代表什麼意思,我們都不知道,我們只負責儲存和查詢。

這些業務資料非常有用,基於這些資料,我們可以做很多的資料報表出來。因為本文是討論 APM 的,所以該部分內容就不再贅述了。

其他

ClickHouse 需要批次寫入,不然肯定是撐不住的,一般一個 batch 至少 10000 行資料。

我們在 Kafka 這層控制了,一個 app_name + ip 的資料,只會被同一個 dog-server 消費,當然也不是說被多個 dog-server 消費會有問題,但是這樣寫入 ClickHouse 的資料就會更多。

還有個關鍵的點,前面我們說了每個 processor 是由單執行緒進行訪問的,但是有一個問題,那就是來自 Dog UI 上的請求可怎麼辦?這裡我想了個辦法,那就是將請求放到一個 Queue 中,由 Kafka Consumer 那個執行緒來消費,它會將任務扔到兩個 Disruptor 中。比如這個請求是 transaction 報表請求,其中一個 Disruptor 的消費者會發現這個是自己要乾的,就會去執行這個任務。

小結

如果你瞭解 Cat 的話,可以看到 Dog 在很多地方和 Cat 有相似之處,或者直接說”抄“也行,之前我們也考慮過直接使用 Cat 或者在 Cat 的基礎上做二次開發。但是我看完 Cat 的原始碼後,就放棄了這個想法,仔細想想,只是借鑑 Cat 的資料模型,然後我們自己寫一套 APM 其實不是很難,所以有了我們這個專案。

行文需要,很多地方我都避重就輕,因為這不是什麼原始碼分析的文章,沒必要處處談細節,主要是給讀者一個全貌,讀者能透過我的描述大致想到需要處理哪些事情,需要寫哪些程式碼,那就當我表述清楚了。

作者:javadoop

覺得本文對你有幫助?請分享給更多人