eBay鄧明:dubbo-go 中 metrics 的設計

最近因爲要在 Apache/dubbo-go(以下簡稱 dubbo-go )裏面實現類似的這個 metrics 功能,於是花了很多時間去了解現在 Dubbo 裏面的 metrics 是怎麼實現的。該部分,實際上是被放在一個獨立的項目裏面,即 metrics 。

總體上來說,Dubbo 的 metrics 是一個從設計到實現都非常優秀的模塊,理論上來說,大部分的 Java 項目是可以直接使用 metrics 的。但也因爲兼顧性能、擴展性等各種非功能特性,所以初看代碼會有種無從下手的感覺。

今天這篇文章將會從比較大的概念和抽象上討論一下 dubbo-go 中的 metrics 模塊的設計——實際上也就是 Dubbo 中的 metrics 的設計。因爲我僅僅是將 Dubbo 裏面的相關內容在 dubbo-go 中複製一份。

目前 dubbo-go 的 metrics 剛剛開始起步,第一個 PR ,點擊這裏

總體設計

Metric

要想理解 metrics 的設計,首先要理解,我們需要收集一些什麼數據。我們可以輕易列舉出來在 RPC 領域裏面我們所關心的各種指標,諸如每個服務的調用次數,響應時間;如果更加細緻一點,還有各種響應時間的分佈,平均響應時間,999線……

但是上面列舉的是從數據的內容上劃分的。 metrics 在抽象上,則是摒棄了這種劃分方式,而是結合了數據的特性和表現形式綜合劃分的。

從源碼裏面很容易找到這種劃分的抽象。

metrics 設計了 Metric 接口作爲所有數據的頂級抽象:

在 Dubbo 裏面,其比較關鍵的子接口是:

爲了大家理解,這裏我抄一下這些接口的用途:

  • Gauge: 一種實時數據的度量,反映的是瞬態的數據,不具有累加性,例如當前 JVM 的線程數;
  • Counter: 計數器型指標,適用於記錄調用總量等類型的數據;
  • Histogram : 直方分佈指標,例如,可以用於統計某個接口的響應時間,可以展示 50%, 70%, 90% 的請求響應時間落在哪個區間內;
  • Meter: 一種用於度量一段時間內吞吐率的計量器。例如,一分鐘內,五分鐘內,十五分鐘內的qps指標;
  • Timer: Timer相當於Meter+Histogram的組合,同時統計一段代碼,一個方法的qps,以及執行時間的分佈情況;

目前 dubbo-go 只實現了 FastCompass ,它也是 Metric 的子類:

這個接口功能很簡單,就是用於收集一段時間之內的 subCategory 執行的次數和響應時間。 subCategory 是一個比較寬泛的概念,無論是在 Dubbo 還是在 dubbo-go 裏面,一個典型的 subCategory 就會是某個服務。

這裏的設計要點在於,它是從什麼角度上去做這些數據的抽象的。

很多人在開發這種採集數據的相關係統或者功能的時候,最容易陷入的就是從數據內容上做抽象,例如抽象一個接口,裏面的方法就是獲得服務的調用次數或者平均響應時間等。

這種抽象並非不可以,尤其是在簡單系統裏面,還非常好用。唯獨在通用性和擴展性上要差很多。

MetricManager

在我們定義了 Metric 之後,很容易就想到,我要有一個東西來管理這些 Metric 。這就是 MetricManager ——對應到 Dubbo 裏面的 IMetricManager 接口。

MetricManager 接口目前在 dubbo-go 裏面還很簡單:

本質上來說,我在前面提到的那些 Metric 的子類,都可以從這個 MetricManager 裏面拿到。它是對外的唯一入口。

因此無論是上報採集的數據,還是某些功能要用這些採集的數據,最重要的就是獲得一個 MetricManager 的實例。例如我們最近正在開發的接入 Prometheus 就是拿到這個 MetriManger 實例,而後從裏面拿到 FastCompass 的實例,而後採集這些數據:

MetricRegistry

MetricRegistry 是一個對 Metric 集合的抽象。 MetricManager 的默認實現裏面,就是使用 MetricRegistry 來管理 Metric 的:

所以,本質上它就是提供了一些註冊 Metric 然後再從裏面撈出來的方法。

於是,這就有一個問題了:爲什麼我在有了 MetricManager 之後,還有有一個MetricRegistry?似乎這兩個功能有些重疊?

答案大概是兩個方面:
1、除了管理所有的 Metric 之外,還承擔着額外的功能,這些功能典型的就是 IsEnabled 。而實際上,在未來我們會賦予它管理生命週期的責任,比如說在 Dubbo 裏面,該接口就還有一個 clear 方法;
2、 metrics 裏面還有一個 group 的概念,而這隻能由 MetricManager 來進行管理,至少交給 MetricRegistry 是不合適的。

metrics 的 group 說起來也很簡單。比如在 Dubbo 框架裏面採集的數據,都會歸屬於 Dubbo 這個 group 。也就是說,如果我想將非框架層面採集的數據——比如純粹的業務數據——分隔出來,就可以借用一個 business group 。又或者我採集到的機器自身的數據,可以將其歸類到 system 這個 group 下。

所以 MetricManger 和 MetricRegistry 的關係是:

Clock

Clock 抽象是一個初看沒什麼用,再看會覺得其抽象的很好。Clock 裏面就兩個方法:

一個是獲得時間戳,另外一個則是獲得時間週期(Tick)。比如通常採集數據可能是每一分鐘採集一次,所以你得知道現在處在哪個時間週期裏面。Clock 就提供了這種抽象。

很多人在實現自己的這種 metrics 的框架的時候,大多數都是直接使用系統的時鐘,也就是系統的時間戳。於是所有的 Metic 在採集數據或者上報數據的時候,不得不自己去處理這種時鐘方面的問題。

這樣不同的 Metric 之間就很難做到時鐘的同步。比如說可能在某個 Metric1 裏面,採集週期是當前這一分鐘,而 Metric2 是當前這一分鐘的第三十秒到下一分鐘的第三十秒。雖然它們都是一分鐘採集一次,但是這個週期就對不上了。

另外一個有意思的地方在於,Clock 提供的這種抽象,允許我們不必真的按照現實時間的時間戳來處理。比如說,可以考慮按照 CPU 的運行時間來設計 Clock 的實現。

例子

就用這一次 PR 的內容來展示一下這個設計。

在 dubbo-go 裏面這次實現了 metricsFilter ,它主要就是收集調用次數和響應時間,其核心是:

report 其實就是把 metrics reports 給 MetricManager :

所以,這裏面可以看出來,如果我們要收集什麼數據,也是要先獲得 MetricManager 的實例。

FastCompass 的實現裏面會將這一次調用的服務及其響應時間保存下來。而後在需要的時候再取出來。

所謂的需要的時候,通常就是上報給監控系統的時候。比如前面的提到的上報給 Prometheus。

所以這個流程可以抽象表達爲:

這是一個更加寬泛的抽象。也就是意味着,我們除了可以從這個 metricFilter 裏面收集數據,也可以從自身的業務裏面去收集數據。比如說統計某段代碼的執行時間,一樣可以使用 FastCompass 。

而除了 Prometheus ,如果用戶自己的公司裏面有監控框架,那麼他們可以自己實現自己的上報邏輯。而上報的數據則只需要拿到 MetricManager 實例就能拿到。

總結

本質上來說,整個 metrics 可以看做是一個巨大無比的 provider-conumer 模型。

不同的數據會在不同的地方和不同時間點上被採集。有些人在讀這些源碼的時候會有點困惑,就是這些數據什麼時間點會被採集呢?

它們只會在兩類時間點採集:
1、實時採集。如我上面舉例的 metricsFilter ,一次調用過來,它的數據就被採集了;
2、另外一個則是如同 Prometheus 。每次 Prometheus 觸發了 collect 方法,那麼它就會把每種(如 Meter, Gauge )裏面的數據收集過來,然後上報,可以稱爲是定時採集;

Dubbo 裏面採集了非常多的數據:

這些具體的實現,我就不一一討論了,大家有興趣可以去看看源碼。這些數據,也是我們 dubbo-go 後面要陸續實現的東西,歡迎大家持續關注,或者來貢獻代碼。


原文鏈接
本文爲阿里雲原創內容,未經允許不得轉載。

發佈了1214 篇原創文章 · 獲贊 82 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章