跨越時代的度量衡 - Pandora.js 的 Metrics 介紹

跨越時代的度量衡 - Pandora.js 的 Metrics 介紹

自秦始皇統一六國,天下歸一,推行“一法度衡石丈尺,車同軌 ,書同文”,頒發統一度量衡詔書,制定了一套嚴格的管理制度,天底下的度量衡就變成了一套。而如今程序世界也是天下分崩離析,不同編程語言各佔一隅,不過即使語法不同,但是分分合合,思路終歸一致,想要度量代碼的心情依舊是一樣的。

度量的作用

很多同學表示懷疑,爲什麼要度量?

其實回答很簡單,度量就像是身體健康的檢查器,就像體檢給的報告,沒有這份報告,你是不是很擔心自己的身體有沒有出問題,要是好幾年沒體檢,恐怕焦慮症都要犯了。

平常我們所說的監控報警,其實就是度量的一種具體應用和泛化,其實還有更大更廣泛作用,比如:

  • 當你上線了一個應用,你怎麼知道應用是健康的?
  • 當你發佈了一個新功能,你怎麼知道這個功能對線上有哪些影響,怎麼去評估呢?
  • 當你升級了一個包版本,你怎麼知道他是穩定的呢?

一切的一切,盡在度量上。

Pandora.js 的度量體系參考了 spring-boot 的命名習慣,以及結合了阿里內部的 alimetrics 體系,加上業界的 opentracing 鏈路追蹤模型,形成了一整套可檢查,可度量,可追蹤的完整方案。

我們今天來細說 Pandora.js 其中的一種度量機制,這不僅是 Node.js 的度量,也是程序通用的度量 - Metrics。

Metric 的命名

Metric 的目標是可自我描述,所以在命名上會儘可能的根據業務場景來命名。

在參考了業界的 metrics規範 ,以及結合了阿里集團內使用場景之後,我們使用了基礎的 MetricName 對象格式做爲 Metric 的名字。

Metrics 是複數形式,目前這些度量在業界口頭表述都可能叫 Metrics ,而實際在代碼中對特定的單個指標叫 Metric,而普通的非特定單數形式繼續沿用 Metrics 的表述 。

MetricName 大體分爲兩部分,key 和 tags。

key 代表着一個具體的項,比如有一個 metrics 指標是某個 HTTP 接口,那麼這個指標的 key 就可能是 application.http.request.path ,通過 . 將命令進一步的縮小,每個公司可以有自己不同的規範,根據部門、產品、功能來進行劃分,儘量做到 key 可描述,可擴展,所有的單詞用下劃線 '_' 連接,字母採用小寫形式。

tags 代表着一個指標的不同分類,它和 key 加起來唯一指定了一個 Metric。tags 是一個對象 {} ,通過不同的 kv 對來描述詳情,比如區分不同請求的來源,繼續以 HTTP 接口來舉例, {"source":"shanghai"} {"source": "hangzhou"} 這樣就是不同的 tags,結合 key,就用來表示不同的 Metric 了。

這樣的好處就是 tags 可以無限擴展,不會影響到 key,同時,在後續的存儲中,同一個 key 可以進行查詢篩選,保證數據一致性和連貫性。

具體的實例我們將在之後的實例中介紹。

對外的數據格式

除了定義 Metric 的名字之外,我們還需要考慮對外輸出的格式,既然是 Node 體系,我們首先考慮的自然是 JSON 結構的格式,便於閱讀以及數據格式化存儲。

基於 MetricName,我們將名字和值定義了成了 通用的 MetricObject 格式

一個標準的輸出格式大概如下:

{
"metric": "sys.cpu.nice",
"timestamp": 1346846400,
"value": 18,
"type": "COUNTER",
"level": "CRITICAL",
"tags": {
"host": "web01",
"dc": "lga"
}
}

和 MetricName 類似,包含一些常用信息,包括 key,value,時間戳,tags 等。

最終這個數據格式會被內置的 Reporter 體系輸出到不同的對外接口中,包括文本文件,HTTP 接口等等,這樣外部系統根據這樣的內容進行存儲,計算,分發,來進行最後的監控,可視化工作,這個不在我們這個體系內,暫時不做過多的介紹了。

Metrics 的類型

Metric 除了有名字,還有類型,目前最常用的就是瞬時值和計數器,此外還有其他的一些。

在瞭解業界實踐並結合集團內部的實踐基礎上,我們抽象出以下幾種度量場景:

  • 累加型度量:對指標的數據進行累加,反映的是數據隨着時間單調遞增的關係,應用接受到的 HTTP 請求的總次數
  • 瞬態型度量:表示指標在當前時間點的瞬時情況,反映的是數據隨着時間上下波動的關係,如系統的load,內存使用率,堆信息等
  • 變化速率度量:表示指標在某個時間段內變化的速率,反映的是數據隨時間的增長快慢關係,如某個接口的 QPS
  • 數據分佈度量:表示某一些指標在某個時間段內的分佈情況,反映的是數據隨時間的統計學分佈關係,如某段時間內,某個接口的 RT 的最大,最小,平均值,方差,95% 分位數等

基於這些場景,結合業界的 Metrics 實現,我們目前提供四種最基礎的指標,即:

  • Gauge 瞬態的度量指標
  • Counter 累加計數型指標
  • Histogram 分佈度量指標
  • Meter 速率度量指標

目前最常用的是 Gauge 瞬態值以及 Counter 累加值,80% 的場景都可以覆蓋

這樣在不同的場景下,我們都可以找到相應的 Metric 類型了。在某些場景下,我們還做了額外的一些指標類型(在阿里內部還有兩種聚合的類型,等機會開源)

Metrics 數據生產

目前 Pandora.js 全部使用 TypeScript 來編寫,有些代碼必須帶類型定義。

所有的 Metric 類型都繼承與 Metric 接口

瞬態型度量

大部分的度量都從瞬態值 Gauge 介紹起,因爲它最簡單,最直觀的表示數據的真實情況,也不涉及時間間隔的問題。

Gauge 只包含一個 getValue 方法,只需要實現這個方法即可,比如,你想要知道當前進程的 CPU 使用情況,就可以一句話解決。

<BaseGauge> {
getValue() {
const startUsage = process.cpuUsage();
return startUsage.user;
}
}

注意,所有的 Metrics 最終輸出的一定是數字形式,這樣纔可度量,如果你希望輸出的是字符串類的信息,我們有另一套輸出體系,這將在之後的文章介紹。

累加型度量

Counter 是第二個介紹的類型,計數器和 Gauge 不太一樣,它是累加型,適用於記錄調用總量等類型的數據,比如某個接口的調用次數。

如下圖是計數器的繼承接口和實現類。

除了基礎的 BaseCounter 實現之外,我們提供了 BucketCounter 分桶計數器。

分桶計數的原理是定義一個時間間隔,將一段時間按照時間間隔分割爲幾個桶,每個桶保存當前時間間隔的計數。

比如時間間隔爲 5s ,桶的總數爲 10 個,那麼 0~5s 爲一個桶,5~10s 爲下一個,以此類推。當計數的執行的時間爲 2s 時,那麼將在第一桶中累加,如果爲 7s 時,那麼將在第二個桶累加,非常容易理解。

在實際場景中,因爲內存限制,不宜保存過多,桶的量會有限制,採用環形隊列存儲同時避免數據的挪動。

舉個常用例子,記錄 Koa 服務的請求數。

// 實際使用需要從 MetricsClient 拿到 BucketCounter
let counter = new BucketCounter();

app.use(async (ctx, next) => {
// 累加 1 counter.inc(1);
counter.inc();
await next()
});

分佈度量

第三個介紹的是 Histogram,直方分佈指標,Pandora.js 包含一個基礎實現類 BaseHistogram , 通過它可以用於統計某個接口的響應時間,可以展示 50%, 70%, 90% 的請求響應時間落在哪個區間內,通過這些你可以計算出 Apdex

這邊的分佈暫時只考慮單機分佈,在集羣維度上不能這樣計算。

對於分佈計算,核心就是維護一個數據集 Reservoir ,數據集用來提供數據存儲以及獲取當前快照的能力。這其中最重要的就是數據更新的策略,目前 Pandora.js 只實現了隨機採樣(UniformReservoir)和 指數衰減隨機採樣(ExponentiallyDecayingReservoir)的實現,由於隨機採樣並不能很好的表現權重問題,默認的是指數衰減隨機採樣,其他的採樣算法沒有實現,有興趣的同學可以補充。

舉個常用例子,記錄 Koa 服務的成功比率,採用隨機採樣算法,間隔 1s,2個分桶,展示獲取了平均數等信息。

// 實際使用需要從 MetricsClient 拿到 BaseHistogram
let histogram = new BaseHistogram(ReservoirType.UNIFORM, 1, 2);

app.use(async (ctx, next) => {
histogram.update(10);
histogram.update(20);

// other biz
});

// let snapshot = histogram.getSnapshot();
// expect(snapshot.getMean()).to.equal(15);
// expect(snapshot.getMax()).to.equal(20);
// expect(snapshot.getMin()).to.equal(10);
// expect(snapshot.getMedian()).to.equal(15);

變化速率度量

第四個介紹的是 Meter,是一種用於度量一段時間內吞吐率的計量器。例如,一分鐘內,五分鐘內,十五分鐘內的qps指標。

這裏要指出,變化的速率,我們一般情況下會關心兩個地方,一個是瞬時爆發,超出平常正常值非常高的這樣的波動變化,另一個是一段時間內的趨勢,從平均的角度來看整體度量的一種方式,這種方式會將高低點進行平均來看。

前一種在 Metrics 中使用 Rate 的概念,只記錄事件的累計總次數,有外部系統來通過前後兩次採集,來計算瞬時速率,這裏我們稱之爲 Rate

在 rate 的計算中,我們認爲數據的增長是 線性 的。其計算方式爲:rate = (v2 - v1) / (t2 - t1),其中時間的單位是 s。

這樣的好處是,通過調整採集頻率,可以支持任意時間間隔的瞬時速率計算。但缺點是,當兩次採樣之間系統重啓的時候,會計算出負數,同時會有一部分數據丟失。

後一種通過指數移動加權平均(Exponential Weighted Moving Average, EWMA)來計算。

針對速率型度量指標,我們提供了 1 分鐘(m1),5 分鐘(m5),15分鐘的EWMA(m15),分別用於反映距離當前時間點 1 分鐘,5 分鐘,15 分鐘的速率變化。

其具體的計算方法,和 Linux 系統中 load1, load5, load15 的計算方法完全一致。即,每 5 秒鐘統計一次瞬時速率,並應用於如下的遞推公式:

EWMA(t) = EWMA(t-1) + alpha * (instantRate - EWMA(t-1))

其中 alpha 取值範圍爲 0~1, 稱爲衰減係數,該係數越大,則距離當前的時間點越老的數據權重衰減的越快。

舉個常用例子,記錄 Koa 某個路由的調用比率。

// 實際使用需要從 MetricsClient 拿到 BaseMeter
let meter = new BaseMeter();

router.get('/home', async (ctx) => {
// 接口調用埋點
meter.mark(1);
});

// meter.getMeanRate(); 總數除以時間
// meter.getOneMinuteRate(); // 一分鐘的 EWMA

本文最後

以上只是 Pandora.js 的度量體系的一部分,結合了阿里自己的 Metrics 體系,只能管中窺豹,簡單的介紹一下幾種最基本的度量指標類型,通過這本的度量器,我們可以將數據從業務代碼中產生出來。

不過這僅僅是數據生成,除此之外,數據採集和加工也非常的重要,下一篇我們將會講到,Pandora.js 的數據採集和加工部分。

Pandora.js 項目地址: https://github.com/midwayjs/pandora 歡迎社會各界前來 Star ~

跨越時代的度量衡 - Pandora.js 的 Metrics 介紹

自秦始皇統一六國,天下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章