Dubbo Metrics 發布新版本 2.0.1 | Dubbo 的度量統計基礎設施

2020-12-12 開源中國

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

 

Dubbo Metrics 的起源

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

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

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

 

Dubbo Metrics 做了哪些改進

相對於 Dropwizard Metrics ,Dubbo Metrics 做的改進主要有以下幾個方面:

 

一、引入基於 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,那麼實現上就會更加複雜了。

通過引入Tag的概念,可以很好的解決了這個問題,我們將 metrics 分為兩個部分,Key 和 Tag。

同時,考慮到一個公司所有微服務產生的所有指標,都會統一匯總到同一個平臺進行處理,因此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 的概念。

 

二、添加精準統計功能

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

在統計吞吐率(如 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 做了如下改進:

 

三、極致性能優化

在大促場景下,如何提升統計性能,對於 Dubbo Metrics 來說是一個重要話題。在阿里的業務場景下,某個統計接口的 QPS 可能達到數萬,例如訪問 Cache 的場景,因此這種情況下 metrics 的統計邏輯很可能成為熱點,我們做了一些針對性的優化:

高並發場景下,數據累加表現最好的就是java.util.concurrent.atomic.LongAdder,因此幾乎所有的操作最好都會歸結到對這個類的操作上。

 

避免調用 LongAdder#reset

當數據過期之後,需要對數據進行清理,之前的實現裡面為了重用對象,使用了LongAdder#reset進行清空,但實測發現LongAdder#reset其實是一個相當耗費cpu的操作,因此選擇了用內存換 CPU,當需要清理的時候用一個新的 LongAdder 對象來代替。

 

去除冗餘累加操作

某些度量器的實現裡面,有些統計維度比較多,需要同時更新多個 LongAdder,例如 DropwizardMetrics的 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%左右。

 

四、全面的指標統計

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

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

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

  • 中間件

  • Tomcat: 請求數,失敗次數,處理時間,發送接收字節數,線程池活躍線程數等;

  • Druid: SQL 執行次數,錯誤數,執行時間,影響行數等;

  • Nginx: 接受,活躍連接數,讀,寫請求數,排隊數,請求QPS,平均 RT 等;

 

後續會陸續添加對Dubbo/Nacos/Sentinel/Fescar等的支持。

 

五、REST支持

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

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

 

六、單機數據落盤

數據如果完全存在內存裡面,可能會出現因為拉取失敗,或者應用本身抖動,導致數據丟失的可能。為了解決該問題,metrics引入了數據落盤的模塊,提供了日誌方式和二進位方式兩種方式的落盤。

 

七、易用性和穩定性優化

  • 將埋點的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 (持續更新)

相關焦點

  • eBay鄧明:dubbo-go 中 metrics 的設計
    最近因為要在 Apache/dubbo-go(以下簡稱 dubbo-go )裡面實現類似的這個 metrics 功能,於是花了很多時間去了解現在 Dubbo 裡面的 metrics 是怎麼實現的。該部分,實際上是被放在一個獨立的項目裡面,即 metrics 。
  • Dubbo 版 Swagger 來啦!Dubbo-Api-Docs 發布
    方法參數為一個 Bean,Bena 中屬性為基礎數據類型的很少的第三方依賴,甚至大部分都是你項目裡本身就使用的。可以通過 profile 決定是否加載,打包時簡單地修改 profile 就能區分生產和測試,甚至 profile 你本來就使用了。今天,我很高興的宣布:Dubbo 用戶也可以享受類似 Swagger 的體驗了 -- Dubbo-Api-Docs 發布了。
  • 官方Dubbo Spring Boot Starter 1.0.0 公測版即將發布
    據阿里開發團隊宣布,Dubbo Spring Boot Starter 1.0.0 公測版已開發完畢,即將發布至 Maven 公有倉庫,目前正在內部測試中。
  • SpringBoot+ Dubbo + Mybatis + Nacos +Seata整合來實現Dubbo分布式事務
    1.簡介「本文主要介紹SpringBoot2.1.5 + Dubbo 2.7.3 + Mybatis 3.4.2 + Nacos 1.1.3 +Seata 0.8.0整合來實現Dubbo分布式事務管理,使用Nacos 作為 Dubbo和Seata的註冊中心和配置中心,使用 MySQL 資料庫和 MyBatis來操作數據。
  • 火了近十年的阿里開源項目 Apache Dubbo...
    服務自省作為 Dubbo 元數據的基礎設施,不僅支持所有元數據的存儲和平滑升級,而且不會對註冊中心、配置中心和元數據中心產生額外的負擔。根據不完全統計,Dubbo 核心 Provider 用通常會暴露 20 ~ 50 個服務接口。註冊中心是中心化的基礎設施,其穩定性面臨嚴峻考驗。儘管微服務架構不斷地深化,然而現實情況是,更多開發者仍舊願意在單一 Provider 上不斷地增加 Dubbo 服務接口,而非更細粒度的 Dubbo Provider 組織。
  • Java Dubbo 框架編譯運行
    從github上倉庫apache/incubator-dubbo下載Dubbo的原始碼,需要系統中已安裝java sdk和maven,java sdk是編譯工具,maven是倉庫依賴管理工具。進入incubator-dubbo目錄,使用maven來編譯軟體包,運行命令mvn package生成軟體包jar文件。
  • 當當網開源 Dubbox,擴展 Dubbo 服務框架支持REST風格遠程調用
    當當網架構部和技術委員會架構師沈理向InfoQ中文站介紹了Dubbox項目,開發背景和主要特點描述如下: Dubbo是一個被國內很多網際網路公司廣泛使用的開源分布式服務框架,即使從國際視野來看應該也是一個非常全面的SOA基礎框架。
  • 告訴你 Dubbo 的底層原理,面試不再怕!
    Dubbo支持的協議很多,包括:dubbo、rmi、hessian、http、webservice、thrift、memcached、redis等。默認使用 dubbo 協議。參考資料:dubbo 中文文檔:http://dubbo.apache.org/zh-cn/docs/user/quick-start.htmlDubbo 實現原理及架構詳解:http://crazyfzw.github.io
  • Spring Cloud與Dubbo優缺點詳解
    從系統結構簡易程度考慮Spring Cloud的系統結構更簡單,「註冊+springmvc=springcloud」,而 Dubbo 各種複雜的 URL、protocol、register、invocation、dubbofilter、dubboSPI,dubbo
  • 史上最全 40 道 Dubbo 面試題及答案,看完碾壓面試官
    1)通信方式不同 Dubbo 使用的是 RPC 通信,而 Spring Cloud 使用的是 HTTP RESTFul 方式。 2)組成部分不同
  • Dubbo的設計理念原來就藏在這三張圖中
    當Dubbo新版本上線後,如果需要進行灰度發布,可以通過dubbo-admin等管理平臺添加路由規則,最終會寫入到指定服務的router節點(持久節點),服務調用方會監聽該節點的變化,從而感知最新的路由規則,將其用於服務提供者的篩選,從而實現灰度發布等功能。configurators 節點的運作機制與 router 節點一樣,就不重複介紹。
  • Spring Cloud Alibaba 發布 GA 版本,新增 4 個模塊
    Spring Cloud Alibaba NacosNacos Client 依賴使用了最新的 1.0.0 版本。Nacos 1.0.0 GA 版本,可大規模投入到生產環境,我們建議升級到 1.0.0 版本。
  • smart-doc 1.9.0 發布,新增 Open Api 3.0+ 支持
    支持從項目外部加載原始碼來生成欄位注釋(包括標準規範發布的jar包)。 支持生成多種格式文檔:Markdown、HTML5、Asciidoctor、Postman collection。 輕易實現在Spring Boot服務上在線查看靜態HTML5 api文檔。 開放文檔數據,可自由實現接入文檔管理系統。 一款代碼注釋檢測工具,不寫注釋的小夥伴逃不過法眼了。
  • Dubbo系列-揚帆起航
    Let's Do It!首先我們定義一個接口和一個簡單實現。service = new AobingServiceImpl ();         AobingRpcFramework.export(service, 2333);         //服務調用者只需要設置依賴       AobingService service = AobingRpcFramework.refer(AobingService.class, "127.0.0.1
  • Hawkular Metrics 0.17.0 發布 - OSCHINA - 中文開源技術交流社區
    0.17.0 發布了,該版本更新主要包括性能增強和全新的Grafana Datasource插件,具體如下:更新日誌:>1.2.POST /hawkular/metrics/metrics/raw/queryThe endpoint accepts a list of metrics
  • 微服務解決方案 Apache ServiceComb 發布 0.3.0 版本
    Apache ServiceComb 1.0.0 發布了,此次更新的部分包括 Apache ServiceComb Java-Chassis
  • ElasticSearch的聚合查詢基礎使用教程之度量(Metric)聚合
    因此,當運行絕對值大於的多頭時,結果可能是近似的2^53。度量(Metric)聚合數值指標聚合是一種特殊類型的指標聚合,可輸出數值。一些聚合輸出單個數值度量(例如avg)並被稱為single-value numeric metrics aggregation,其他聚合則生成多個度量(例如stats)並被稱為multi-value numeric metrics aggregation。
  • smart-doc 1.9.4 發布,Java 零註解 API 文檔生成工具
    smart-doc是一款同時支持java restful api和apache dubbo rpc接口文檔生成的工具
  • smart-doc 2.0.1 發布,Java 零註解 API 文檔生成工具
    smart-doc是一款同時支持java restful api和apache dubbo rpc接口文檔生成的工具