探祕 Dubbo 的度量統計基礎設施 - Dubbo Metrics

markus_spiske_711232_unsplash

對服務進行實時監控,瞭解服務當前的運行指標和健康狀態,是微服務體系中不可或缺的環節。Metrics 做爲微服務的重要組件,爲服務的監控提供了全面的數據基礎。近日,Dubbo Metrics 發佈了2.0.1版本,本文將爲您探祕 Dubbo Metrics 的起源,及 7 大改進。java

Dubbo Metrics 的起源

Dubbo Metrics(原Alibaba Metrics)是阿里巴巴集團內部普遍使用的度量埋點基礎類庫,有 Java 和 Node.js 兩個版本,目前開源的是 Java 版本。內部版本誕生於2016年,經歷過近三年的發展和雙十一的考驗,已經成爲阿里巴巴集團內部微服務監控度量的事實標準,覆蓋了從系統、JVM、中間件到應用各層的度量,而且在命名規則、數據格式、埋點方式和計算規則等方面,造成了一套統一的規範。git

Dubbo Metrics 的代碼是基於 Dropwizard Metrics 衍生而來,版本號是3.1.0,當時決定 fork 到內部進行定製化開發的主要緣由有兩個。github

一是社區的發展很是緩慢,3.1.0以後的第3年才更新了下一個版本,咱們擔憂社區沒法及時響應業務需求;另外一個更重要的緣由是當時的3.1.0還不支持多維度的 Tag,只能基於 a.b.c 這樣傳統的指標命名方法,這就意味着 Dropwizard Metrics 只能在單維度進行度量。而後,在實際的業務過程當中,不少維度並不能事先肯定,並且在大規模分佈式系統下,數據統計好之後,須要按照各類業務維度進行聚合,例如按機房、分組進行聚合,當時的 Dropwizard 也沒法知足,種種緣由使得咱們作了一個決定,內部fork一個分支進行發展。spring

Dubbo Metrics 作了哪些改進

相對於 Dropwizard Metrics ,Dubbo Metrics 作的改進主要有如下幾個方面:數據庫

1、引入基於 Tag 的命名規範
如前文所描述,多維度 Tag 在動態埋點,數據聚合等方面相對於傳統的 metric 命名方法具備自然的優點,這裏舉一個例子,要統計一個 Dubbo 服務 DemoService 調用次數和 RT,假設這個服務叫作 DemoService,那麼按照傳統的命名方式,一般會命名爲dubbo.provider.DemoService.qpsdubbo.provider.DemoService.rt。若是隻有1個服務的話,這種方法並沒有太大的問題,可是若是一個微服務應用同時提供了多個 Dubbo 的 Service,那麼要聚合統計全部Service 的 QPS 和 RT 就比較困難了。因爲 metric 數據具備自然的時間序列屬性,所以數據很是適合存儲到時間序列數據庫當中,要統計全部的 Dubbo 服務的 QPS,那麼就須要查找全部名稱爲dubbo.provider.*.qps的指標,而後對他們進行求和。因爲涉及到模糊搜索,這對於絕大部分數據庫的實現都是比較費時的。若是要統計更加詳細的 Dubbo 方法級別的 QPS 和 RT,那麼實現上就會更加複雜了。緩存

1551319767985_7d76a362_1e92_4897_b527_95d9a3b4b5cd

  • Metric Key:用英文點號分隔的字符串,來表徵這個指標的含義
  • Metric Tag:定義了這個指標的不一樣切分維度,能夠是單個,也能夠是多個;
  • tag key:用於描述維度的名稱;
  • tag value:用於描述維度的值;

同時,考慮到一個公司全部微服務產生的全部指標,都會統一彙總到同一個平臺進行處理,所以Metric Key 的命名方式爲應當遵循同一套規範,避免發生命名衝突,其格式爲appname.category[.subcategory]*.suffix性能優化

  • appname: 應用名;
  • category: 這個指標在該應用下的分類,多個單詞用'_'鏈接,字母採用小寫;
  • subcategory: 這個指標在該應用下的某個分類下的子分類,多個單詞用'_'鏈接,字母採用小寫;
  • suffix: 這個關鍵的後綴描述了這個指標所度量的具體類型,能夠是計數,速率,或者是分佈;

在上述例子中,一樣的指標能夠命名爲dubbo.provider.service.qps{service="DemoService"},其中前面部分的名稱是固定的,不會變化,括號裏面的Tag,能夠動態變化,甚至增長更多的維度,例如增長 method 維度dubbo.provider.service.qps{service="DemoService",method="sayHello"},也能夠是機器的 IP、機房信息等。這樣的數據存儲是時間序列數據庫親和的,基於這些數據能夠輕鬆實現任意維度的聚合,篩選等操做。多線程

P.s. 2017年12月底,Dropwizard Metrics4.0 開始支持 Tag,Dubbo Metrics 中 ag 的實現參考了Dropwizard。spring-boot 2.0中提供的 MicroMeter 和 Prometheus 也均已引入了 Tag 的概念。併發

2、添加精準統計功能
Dubbo Metrics 的精準統計是和 Dropwizard,或者其餘開源項目埋點統計庫實現不太同樣的地方。下面分別經過時間窗口的選擇和吞吐率統計方式這兩個緯度進行闡述。app

在統計吞吐率(如 QPS)的時候,Dropwizard的實現方式是滑動窗口+指數加權移動平均,也就是所謂的EWMA,在時間窗口上只提供1分鐘、5分鐘、15分鐘的選擇。

固定窗口 vs 滑動窗口
在數據統計的時候,咱們須要事先定義好統計的時間窗口,一般有兩種確立時間窗口的方法,分別是固定窗口和滑動窗口。

固定時間窗口指的是以絕對時間爲參考座標系,在一個絕對時間窗口內進行統計,不管什麼時候訪問統計數據,時間窗口不變;而滑動窗口則是以當前時間爲參考系,從當前時間往前推一個指定的窗口大小進行統計,窗口隨着時間,數據的變化而發生變化。

固定窗口的優勢在於:一是窗口不須要移動,實現相對簡單;二是因爲不一樣的機器都是基於相同的時間窗口,集羣聚合起來比較容易,只需按照相同的時間窗口聚合便可。其缺點是:若是窗口較大,實時性會受到影響,沒法當即獲得當前窗口的統計信息。例如,若是窗口爲1分鐘,則必須等到當前1分鐘結束,才能獲得這1分鐘內的統計信息。

滑動窗口的優勢在於實時性更好,在任意時刻,能都看到當前時刻往前推演一個時間窗口內統計好的信息。相對而言,因爲不一樣機器的採集時刻不一樣,要把不一樣機器上的數據聚合到一塊兒,則須要經過所謂的 Down-Sampling 來實現。即按照固定時間窗口把窗口內收集到的數據應用到某一個聚合函數上。舉個例子來講,假設集羣有5臺機器,按照15秒的頻率按照平均值進行 Down-Sampling,若在00:00~00:15的時間窗口內,在00:01,00:03,00:06,00:09,00:11各收集到一個指標數據,則把這5個點的加權平均認爲是00:00這個點的通過 Down- Sampling 以後的平均值。

但在咱們的實踐過程當中,滑動窗口仍會遇到了如下問題:

  • 不少業務場景都要求精確的時間窗口的數據,好比在雙11的時候,想知道雙11當天0點第1秒建立了多少訂單,這種時候 Dropwizard 的滑動窗口很明顯就不適用了。
  • Dropwizard 提供的窗口僅僅是分鐘級,雙11的場景下須要精確到秒級。
  • 集羣數據聚合的問題,每臺機器上的滑動時間窗口可能都不同,數據採集的時間也有間隔,致使聚合的結果並不許確。

爲了解決這些問題,Dubbo Metrics 提供了 BucketCounter 度量器,能夠精確統計整分整秒的數據,時間窗口能夠精確到1秒。只要每臺機器上的時間是同步的,那麼就能保證集羣聚合後的結果是準確的。同時也支持基於滑動窗口的統計。

瞬時速率(Rate) vs 指數移動加權平均(EWMA)
通過多年的實踐,咱們逐漸發現,用戶在觀察監控的時候,首先關注的實際上是集羣數據,而後纔是單機數據。然而單機上的吞吐率其實並無太大意義。怎麼理解呢?

好比有一個微服務,共有2臺機器,某個方法在某一段時間內產生了5次調用,所花的時間分別是機器1上的[5,17],機器2上的[6,8,8](假設單位爲毫秒)。若是要統計集羣範圍內的平均 RT,一種方法能夠先統計單機上的平均 RT,而後統計總體的平均 RT,按照這種方法,機器1上平均 RT 爲11ms,機器2的平均 RT 爲7.33ms,二者再次平均後,獲得集羣平均 RT 爲9.17ms,但實際的結果是這樣嗎?

若是咱們把機器1和機器2上的數據總體拉到一塊兒總體計算的話,會發現實際的平均 RT 爲(5+17+6+8+8)/5=8.8ms,二者相差很明顯。並且考慮到計算浮點數的精度丟失,以及集羣規模擴大,這一偏差會越發明顯。所以,咱們得出一個結論:單機上的吞吐率對於集羣吞吐率的計算沒有意義,僅在在單機維度上看纔是有意義的。

而 Dropwizard 提供的指數加權移動平均其實也是一種平均,同時考慮了時間的因素,認爲距離當前時間越近,則數據的權重越高,在時間拉的足夠長的狀況下,例如15分鐘,這種方式是有意義的。而經過觀察發現,其實在較短的時間窗口內,例如1s、5s,考慮時間維度的權重並無太大的意義。**所以在內部改造的過程當中,Dubbo Metrics 作了以下改進:
**

  • 提供瞬時速率計算,反應單機維度的狀況,同時去掉了加權平均,採用簡單平均的方式計算;
  • 爲了集羣聚合須要,提供了時間窗口內的總次數和總 RT 的統計,方便精確計算集羣維度的吞吐率;

3、極致性能優化
在大促場景下,如何提高統計性能,對於 Dubbo Metrics 來講是一個重要話題。在阿里的業務場景下,某個統計接口的 QPS 可能達到數萬,例如訪問 Cache 的場景,所以這種狀況下 metrics 的統計邏輯極可能成爲熱點,咱們作了一些針對性的優化:

高併發場景下,數據累加表現最好的就是java.util.concurrent.atomic.LongAdder,所以幾乎全部的操做最好都會歸結到對這個類的操做上。

避免調用 LongAdder#reset
當數據過時以後,須要對數據進行清理,以前的實現裏面爲了重用對象,使用了LongAdder#reset進行清空,但實測發現LongAdder#reset實際上是一個至關耗費cpu的操做,所以選擇了用內存換 CPU,當須要清理的時候用一個新的 LongAdder 對象來代替。

去除冗餘累加操做
某些度量器的實現裏面,有些統計維度比較多,須要同時更新多個 LongAdder,例如 Dropwizard Metrics的 meter 實現裏面計算1分/5分/15分移動平均,每次須要更新3個 LongAdder,但實際上這3次更新操做是重複的,只須要更新一次就好了。

RT爲0時避免調用Add方法
大多數場景下對 RT 的統計都以毫秒爲單位,有些時候當 RT 計算出來小於1ms的時候,傳給metrics的 RT 爲0。當咱們發現 JDK 原生的 LongAdder 並無對add(0)這個操做作優化,即便輸入爲0,仍是把邏輯都走一遍,本質上調用的是sun.misc.Unsafe.UNSAFE.compareAndSwapLong。若是這個時候,metrics 判斷 RT 爲0的時候不對計數器進行 Add 操做,那麼能夠節省一次 Add 操做。這對於併發度較高的中間件如分佈式緩存頗有幫助,在咱們內部某個應用實測中發現,在30%的狀況下,訪問分佈式緩存的 RT 都是0ms。經過這個優化能夠節約大量無心義的更新操做。

QPS 和 RT 合併統計
只須要對一個Long的更新,便可實現同時對調用次數和時間進行統計,已經逼近理論上的極限。

通過觀測發現,一般對於中間件的某些指標,成功率都很是高,正常狀況下都在100%。爲了統計成功率,須要統計成功次數和總次數,這種狀況下幾乎同樣多,這樣形成了必定的浪費,白白多作了一次加法。而若是反過來,只統計失敗的次數,只有當失敗的狀況纔會更新計數器,這樣能大大下降加法操做。

事實上,若是咱們把每一種狀況進行正交拆分,例如成功和失敗,這樣的話,總數能夠經過各類狀況的求和來實現。這樣能進一步確保一次調用只更新一次計數。

但別忘了,除了調用次數,還有方法執行 RT 要統計。還能再下降嗎?

答疑是能夠的!假設 RT 以毫秒爲單位進行統計,咱們知道1個 Long 有64個bits(實際上Java裏面的Long是有符號的,因此理論上只有63個 bits 可用),而 metrics 的一個統計週期最多隻統計60s的數據,這64個 bits 不管怎樣用都是用不掉的。那能不能把這63個 bits 拆開來,同時統計 count 和 RT 呢?其實是能夠的。

咱們把一個 Long 的63個 bits 的高25位用來表示一個統計週期內的總 count,低38位用於表示總 RT。

------------------------------------------
|   1 bit    |     25 bit   |     38 bit |
| signed bit |  total count |   total rt |
------------------------------------------

當一次調用過來來的時候,假設傳過來的 RT 是n,那麼每次累加的數不是1,也不是n,而是

1 * 2^38 + n

這麼設計主要有一下幾個考慮:

  • count是每調用一次加一,RT 是每調用一次加N的操做,若是 count 在高位的話,每次加一,實際是一個固定的常數,而若是rt放在高位的話,每次都加的內容不同,因此每次都要計算一次;
  • 25 bits 最多能夠表示 2^25 = 33554432 個數,因此1分鐘之內對於方法調用的統計這種場景來講確定是不會溢出的;
  • RT 可能會比較大,因此低位給了38bits, 2^38=274877906944 基本也是不可能超的。

若是真的overflow了怎麼辦? 
因爲前面分析過,幾乎不可能overflow,所以這個問題暫時沒有解決,留待後面在解決。

無鎖 BucketCounter
在以前的代碼中,BucketCounter 須要確保在多線程併發訪問下保證只有一個線程對 Bucket 進行更新,所以使用了一個對象鎖,在最新版本中,對 BucketCounter 進行了從新實現,去掉了原來實現中的鎖,僅經過 AtomicReference 和 CAS 進行操做,本地測試發現性能又提高了15%左右。

4、全面的指標統計

Dubbo Metrics 全面支持了從操做系統,JVM,中間件,再到應用層面的各級指標,而且對統一了各類命名指標,能夠作到開箱即用,並支持經過配置隨時開啓和關閉某類指標的收集。目前支持的指標,主要包括:

操做系統
支持Linux/Windows/Mac,包含CPU/Load/Disk/Net Traffic/TCP。

JVM
支持classload, GC次數和時間, 文件句柄,young/old區佔用,線程狀態, 堆外內存,編譯時間,部分指標支持自動差值計算。

中間件
Tomcat: 請求數,失敗次數,處理時間,發送接收字節數,線程池活躍線程數等;
Druid: SQL 執行次數,錯誤數,執行時間,影響行數等;
Nginx: 接受,活躍鏈接數,讀,寫請求數,排隊數,請求QPS,平均 RT 等;

更詳細的指標能夠參見這裏, 後續會陸續添加對Dubbo/Nacos/Sentinel/Fescar等的支持。

5、REST支持

Dubbo Metrics 提供了基於 JAX-RS 的 REST 接口暴露,能夠輕鬆查詢內部的各類指標,既能夠獨立啓動HTTP Server提供服務(默認提供了一個基於Jersey+ sun Http server的簡單實現),也能夠嵌入已有的HTTP Server進行暴露指標。具體的接口能夠參考這裏:

https://github.com/dubbo/metrics/wiki/query-from-http

6、單機數據落盤

數據若是徹底存在內存裏面,可能會出現由於拉取失敗,或者應用自己抖動,致使數據丟失的可能。爲了解決該問題,metrics引入了數據落盤的模塊,提供了日誌方式和二進制方式兩種方式的落盤。

  • 日誌方式默認經過JSON方式進行輸出,能夠經過日誌組件進行拉取和聚合,文件的可讀性也比較強,可是沒法查詢歷史數據;
  • 二進制方式則提供了一種更加緊湊的存儲,同時支持了對歷史數據進行查詢。目前內部使用的是這種方式。

7、易用性和穩定性優化

  • 將埋點的API和實現進行拆分,方便對接不用的實現,而用戶無需關注;
  • 支持註解方式進行埋點;
  • 借鑑了日誌框架的設計,獲取度量器更加方便;
  • 增長Compass/FastCompass,方便業務進行經常使用的埋點,例如統計qps,rt,成功率,錯誤數等等指標;
  • Spring-boot-starter,即將開源,敬請期待;
  • 支持指標自動清理,防止長期不用的指標占用內存;
  • URL 指標收斂,最大值保護,防止維度爆炸,錯誤統計致使的內存。

如何使用

使用方式很簡單,和日誌框架的Logger獲取方式一致。

Counter hello = MetricManager.getCounter("test", MetricName.build("test.my.counter"));
hello.inc();

支持的度量器包括:

  • Counter(計數器)
  • Meter(吞吐率度量器)
  • Histogram(直方分佈度量器)
  • Gauge(瞬態值度量器)
  • Timer(吞吐率和響應時間分佈度量器)
  • Compass(吞吐率, 響應時間分佈, 成功率和錯誤碼度量器)
  • FastCompass(一種快速高效統計吞吐率,平均響應時間,成功率和錯誤碼的度量器)
  • ClusterHistogram(集羣分位數度量器)

後續規劃

  • 提供Spring-boot starter
  • 支持Prometheus,Spring MicroMeter
  • 對接Dubbo,Dubbo 中的數據統計實現替換爲 Dubbo Metrics
  • 在 Dubbo Admin 上展現各類 metrics 數據
  • 對接 Dubbo 生態中的其餘組件,如Nacos, Sentinel, Fescar等

參考資料

Dubbo Metrics @Github:
https://github.com/dubbo/metrics

Wiki: 
https://github.com/dubbo/metrics/wiki (持續更新)

本文做者:

望陶,GitHub ID @ralf0131,Apache Dubbo PPMC Member,Apache Tomcat PMCMember,阿里巴巴技術專家。

子觀,GitHub ID @min,Apache Dubbo Commiter,阿里巴巴高級開發工程師,負責 Dubbo Admin 和 Dubbo Metrics 項目的開發和社區維護。

原文連接

相關文章
相關標籤/搜索