深入OpenTelemetry指標
譯註:在本文中,為理解的一致性,我們保留了OpenTelemetry的術語,不作翻譯:包括但不限於Instrument、Measurement、Exemplar、Baggage、Context等。需要注意區分Measurement和Instrument的區別,前者指的是度量資料,而後者通常是指用於監測的程式碼和工具。
OpenTelemetry 是由 雲原生計算基金會(CNCF) 託管的一個用於基礎設施效能檢測的開源可觀測性框架。該專案在幾乎所有主要的雲供應商(AWS、Google、Microsoft)以及可觀測性供應商(包括Timescale)的貢獻下得到了極大的發展,以至於它成為 按活躍度和貢獻者排名第二的CNCF專案 ,僅次於Kubernetes本身。
OpenTelemetry旨在 為所有型別的可觀測資料定義一個單一的標準 (在OpenTelemetry的術語中被稱作Signals訊號),包括監控指標、日誌和鏈路追蹤。通過一系列的工具、庫、API、SDK和exporters,OpenTelemetry從根本上簡化了從服務中收集訊號並將其傳送到你所選用的後端服務的過程,為更多的使用者和供應商打開了可觀測性的大門。
在設計OpenTelemetry指標標準時,有三個 設計目標 被採納:
- 提供將監控指標與其他型別的可觀察性資料相連線的能力:要麼是通過 Exemplars 在鏈路追蹤和監控指標之間直接連線,要麼是通過 Baggage 和 Context 提供與日誌和鏈路追蹤享有相同的相關元資料,間接地連線。
- 允許從 OpenCensus 標準輕鬆遷移到OpenTelemetry標準。
- 在可能的情況下提供對其他主要指標實現的全面支援。Prometheus和 StatsD 是專門適配的,能夠完全支援:那些遷移到OpenTelemetry的使用者可以看到與使用其原生客戶端類似的結果。
OpenTelemetry提供了一個採集器,可以用來重新聚合和重定向指標資料,經常被用來建立訊號管道。由於OpenTelemetry不提供後端實現(它關注的是建立、收集和傳送訊號),資料將流向另一個或多個系統進行儲存並最終能夠被查詢。之所以OpenTelemetry有時會令人感到複雜,是因為它可以用來模擬許多不同的訊號實現。在這篇博文中,我們將聚焦於”表面”,即開發者在OpenTelemetry中使用指標時可能遇到的要素。
OpenTelemetry指標
OpenTelemetry指標與 Prometheus指標 略有不同,它允許在採集過程中進行更加靈活的轉換,並支援許多匯出型別,包括Pull和Push模式。由於這種靈活性,許多現有的指標系統都可以用OpenTelemetry建模,而不會損失語義或精確性,這使它成為互操作性的完美指標系統。
OpenTelemetry有三個模型:
- 事件模型:在這個模型中,你作為一個開發者來建立指標,
- 流模型:OpenTelemetry使用該模型進行傳輸,
- 時間序列模型,OpenTelemetry將其用於儲存。
在這篇文章中討論的指標都是事件模型的一部分,而轉換則是從事件模型到流模型的轉換的一部分。(作為一個開發者,你其實不需要擔心這些模型,但有一個基本的瞭解會更有幫助)。
OpenTelemetry SDK允許我們:
- 通過時間上的聚合(改變解析度)減少傳輸的指標數量,
- 通過空間聚合減少傳輸的指標數量(去除不需要的屬性),
- 從累積表示法(Prometheus使用的)改為差值表示法(表達數值之間的變化,而不是絕對測量值)。
OpenTelemetry指標的工作方式是使用全域性的 MeterProvider 來建立一個 Meter ,並將其與一個或多個 Instrument 相關聯,每一個Instrument都被用來建立一系列的 Measurements 。這些測量值在 View 中被彙總為一個指標。然後通過 Metric Reader 和 Metric Exporter 的組合來觀察和匯出指標(可以是拉或推)。
圖示說明OpenTelemetry中MeterProvider的要素
Instrument或Measurements是我們在應用中建立或觀察到的東西,而指標則表達了我們與可觀測性資料的消費者分享的該監測的當前聚合值。
OpenTelemetry允許以多種方式將屬性(標籤)附加到指標上。最常見的有:
- 來自任何附加的資源,它可能持有定義主機的標籤。例如,一個Kubernetes Pod或一個虛擬機器
- 來自當前的上下文,它將被附加到所有同步的Instrument上
- Measurement本身所具備的
從Measurements到Metrics
Measurements的建立非常快,尤其是在同步的情況下,但這可能會迅速壓垮一個指標管道。為了解決這個問題,Prometheus使用了一個帶有抓取間隔的Pull機制,而OpenTelemetry通過給每個Instrument附加一個Aggregation View來解決收集路徑中的問題,然後將資料傳遞給觀察它們的MetricReader和輸出它們的MetricExporter:
從測量到指標:OpenTelemetry中指標的收集路徑圖
MetricReader負責在Instrument沒有View的情況下附加預設的View,也負責定義MetricExporters,然後向其傳送這些觀測值。MetricReader還將改變指標的時間性,從預設的Cumulative(新值累加到最後一個值上,這與我們在Prometheus中看到的相同)改為Delta(度量值是新舊值之差)。
每個Instrument都與一個MetricReader相關聯,如果沒有為一個Instrument定義其他View,它就負責附加預設View。此外,它還定義了時間性:可能從預設的Cumulative(當前值加到以前的值,如Prometheus中使用的)切換到Delta(報告當前觀測值和最後觀測值之間的差異,減少計算速率時客戶端的開銷)。
MetricReader和MetricExporter的組合決定了資料如何被髮送到下游。一個非常流行的方法是使用PeriodicExportingMetricReader和OTLPMetricExporter,每隔一段時間(預設為60秒)對指標進行取樣,並將其傳送到OpenTelemetry採集器(它將使用另一個Exporter)進行進一步處理。許多其他的匯出器可用於各種語言。
一些流行的匯出器有:
- Prometheus Metric Exporter :一個基於Pull模式的匯出器,Prometheus客戶端可以抓取它的端點
- Prometheus Remote Write Exporter :一個基於Push模式的匯出器,通過Prometheus遠端寫入協議傳送資料
- OTLPMetricExporter :它可以向任何理解OpenTelemetry協議的裝置推送指標
- ConsoleMetricExporter :它被用來向控制檯輸出除錯資訊
在Python中,初始化OpenTelemetry指標並附加一個預設的MetricReader和MetricExporter(傳送指標到本地OpenTelemetry採集器),程式碼看起來像這樣:
from opentelemetry._metrics import get_meter_provider, set_meter_provider from opentelemetry.exporter.otlp.proto.grpc._metric_exporter import ( OTLPMetricExporter, ) from opentelemetry.sdk._metrics import MeterProvider from opentelemetry.sdk._metrics.export import PeriodicExportingMetricReader exporter = OTLPMetricExporter(insecure=True) reader = PeriodicExportingMetricReader(exporter) provider = MeterProvider(metric_readers=[reader]) set_meter_provider(provider)
監測併發送Measurements
OpenTelemetry提供了六種型別的Instruments,我們可以用它們來捕獲測量結果。它們可以被分為兩大類:同步和非同步。每個Instrument都可以傳送測量值,每個Instrument都可以與屬性相關聯。同步Instrument以類似於Prometheus指標的方式在應用程式程式碼中實現,通過在應用程式中插入程式碼,每次執行時都會更新一個值。它們可以與當前的Context上下文相關聯(這有助於描述當前的應用狀態)。
非同步Instrument則會註冊一個回撥函式,只在觀察時傳送數值。例如,可以註冊一個非同步Instrument,每10秒報告一個感測器的值。這些Instrument不能與當前的Context相關聯,因為它們位於主程式的外部,而不是在主程式執行時被呼叫,它們根據觀察者的要求觀察訊號資料。在某些方面,它們類似於Prometheus通過Exporter對一個未被檢測的應用程式進行監控。
所有Instrument的建立都有一個名稱、一個描述和一個測量單位(必須遵循 Instrument單位規則 )。非同步Instrument還指定了回撥函式,該函式被呼叫以觀察測量結果。開發人員可以使用的OpenTelemetry Instrument型別,如下表所示。(令人困惑的是,語言呈現給使用者的建議名稱與測量名稱不一樣,用Observable來代替Asynchronous。即observable_counter)。
下表概述了OpenTelemetry中Instrument型別的名稱和特點。
現在,讓我們來講一下每一種Instrument型別。
Counter / Asynchronous Counter
> Counter可譯為”計數器”,則Asynchronous Counter譯為”非同步計數器”
Counter是一個同步Instrument,它總是在增加,即它是單調的,只接受非負的值。不出所料,它與Prometheus的Counter是相同的。Counter通過接收增量或Delta差值來工作。
當使用計數器時,語言SDK中會有一個 add 的操作,必須提供一個非負數來增加計數器的值。同時可以附加一組可選的屬性。這些屬性類似於Prometheus的標籤。
非同步計數器的不同之處在於通過回撥而不是 add 函式來操作。當觀察Instrument時,回撥函式會被執行,並將傳回一個或多個測量值,表示為絕對值(不是delta值)。一旦這些值被傳遞,它們將在內部通過計算得到delta值。
例如,你可以實現一個非同步計數器,報告應用程式自啟動以來所消耗的CPU時間。這一資訊將在回撥中從作業系統中提取並返回。可以一次性返回幾個值,比如每個CPU或執行緒都有一個值。這些測量值總是被期望能夠通過一種有意義的方式進行跨屬性求和(在這種情況下,我們可以得到系統使用的總CPU時間)。
請注意,由於Python SDK還不穩定,我們需要匯入_metrics,而不是本文程式碼示例的metrics。未來可能還會有一些破壞性的變更。隨著專案的進展,我們會保持更新。目前的例子是用OpenTelemetry Python SDK v1.11.1編寫的。
在Python中,建立和使用計數器和非同步計數器的例子看起來是這樣的:
from opentelemetry._metrics import get_meter_provider from opentelemetry._metrics.observation import Observation meter = get_meter_provider().get_meter("otel-demo") # Counter counter = meter.create_counter("counter") # This adds 100 to the total (a delta value) counter.add(100,{"app": "timescale"}) # Callback function for Async counter def observable_counter_func() -> [Observation]: # This reports that the current value is 10, which will be # converted to a delta internally return [Observation(10, {"app": "timescale"}] # Async Counter observable_counter = meter.create_observable_counter( "observable_counter", [observable_counter_func] )
UpDownCounter / Asynchronous UpDownCounter
UpDownCounter是一個類似於Counter的同步Instrument,但它允許傳遞負的delta值(它可以不是單調的)。Counter適合表示已經提交的作業數量,而UpDownCounter則適合表示當前正在處理的作業數量(它可以向上和向下移動)。需要注意的是,這與Prometheus中的Gauge的用法不一樣,因為我們記錄的是變化,而不是絕對值。
一個UpDownCounter提供了一個 add 操作,與Counter操作相同:與之相反的是它接受負的資料值。由屬性資料關聯的值被期望是可以求和的。
不出所料,非同步UpDownCounter提供了一個回撥介面,返回一個或多個測量值,將每個測量值表達為一個絕對值,該值將在內部被計算為delta值。
OpenTelemetry規範指出,當被返回的值很容易被觀察到時,不應該使用同步的UpDownCounter。在這種情況下,應該使用一個非同步UpDownCounter來代替。
在Python中,建立和使用UpDownCounter和Asynchronous UpDownCounter的例子看起來是這樣的:
from opentelemetry._metrics import get_meter_provider from opentelemetry._metrics.observation import Observation meter = get_meter_provider().get_meter("otel-demo") # UpDownCounter up_down_counter = meter.create_up_down_counter("up_down_counter") # This adds 100, then removes 10 from the total (both delta values) up_down_counter.add(100,{"app": "timescale"}) up_down_counter.add(-10,{"app": "timescale"}) # Callback function for Async counter def observable_up_down_counter_func() -> [Observation]: # This reports that the current value is 10, which will be # converted to a delta internally return [Observation(10, {"app": "timescale"})] # Async UpDownCounter, note the observable prefix observable_up_down_counter = meter.create_observable_up_down_counter( "observable_up_down_counter", [observable_up_down_counter_func] )
Histogram
> Histogram可譯為直方圖
Histogram是一種同步Instrument,它允許記錄在統計上相互關聯的多個數值。當你不想孤立地分析資料點,而是想通過跟蹤落在每個預定義桶中的值的數量以及最小值和最大值(如果有進行配置)來生成關於其分佈的統計資訊時,你會選擇Histogram。
直方圖只有一個單一的方法被暴露出來: record 。記錄需要一個非負的觀察值,並可以附加一組可選的屬性。
當你檢視HTTP響應時間時,你可能會選擇直方圖:知道每個請求發生時的確切響應時間可能並不那麼有用(而且更適合鏈路追蹤資料,它將暴露每個請求的開始和結束時間),但從服務層面來看,能夠報告中位響應時間和超過95分位數的HTTP請求數量可能更有意思。
需要注意的是,記錄測量結果並不能建立Histogram;預設的Aggregation(Explicit Bucket Histogram Aggregation)可以。當使用Histogram Instrument時,重要的是確保桶也被配置了。預設值是[ 0, 5, 10, 25, 50, 75, 100, 250, 500, 1000 ],而這並不總是理想的。你可以在下面幾段看到一些建立View的例子。
在Python中,一個建立和使用直方圖的例子是這樣的:
from opentelemetry._metrics import get_meter_provider from opentelemetry._metrics.observation import Observation meter = get_meter_provider().get_meter("otel-demo") # Histogram histogram = meter.create_histogram("histogram") # This records a value of 100 histogram.record(100,{"app": "timescale"})
Asynchronous Gauge
非同步Gauge在OpenTelemetry API中的獨特之處在於兩個方面:它沒有同步的實現,並且被設計用來表示那些相加沒有意義的值,即使它們共享屬性資料。這方面的一個例子是一個房子的各個房間的溫度。這是常見的資料,但是把它作為一個總值來報告沒有任何意義--你可能像求一個平均值或最大值,但絕不是一個總和。這是與Prometheus不同的方法,Prometheus將這些型別的要求編碼到指標的命名規則中。在OpenTelemetry中,如果你使用一個Asynchronous Gauge,你不能像使用其他指標型別那樣對其進行彙總。
與所有的非同步Instrument一樣,在建立非同步Instrument時,需要傳遞一個回撥,它可以返回一個或多個(在這種情況下是完全離散的)測量值。
在Python中,建立和使用Asynchronous Gauge的例子是這樣的:
from opentelemetry._metrics import get_meter_provider from opentelemetry._metrics.observation import Observation meter = get_meter_provider().get_meter("otel-demo") # Callback function for Async gauge def observable_gauge_func() -> [Observation]: # This reports that the current value is 10 return [Observation(10, {"app": "timescale"})] # Async Gauge, note the observable prefix observable_gauge = meter.create_observable_gauge( "observable_gauge", [observable_gauge_func] )
Views和Aggregations
OpenTelemetry中的View定義了一個Aggregation操作,它接受一系列的Measurements,並在該時間點上將其表達為一個單一的指標值。隨著更多的Measurements被建立,指標的值會被持續更新。如果沒有為一個Instrument建立View,那麼就會根據Instrument型別選擇一個預設的Aggregation。自定義View可以通過Meter名稱、Instrument名稱、Instrument型別或萬用字元來定位。
在OpenTelemetry中,有三種Aggregation型別:
- Sum Aggregation :簡單地跟蹤傳入的測量值的總和(尊重輸入Instrument的單調性),
- Last Value Aggregation :跟蹤最後報告的值,
- Explicit Bucket Histogram Aggregation :跟蹤屬於每個預定義桶(必須在建立View時預定義)的測量數量,並可以跟蹤最小和最大值。
下表定義了每種OpenTelemetry Instrument型別的預設Aggregation:
這段Python程式碼使用ConsoleMetricExporter寫入控制檯,也改變了所有柱狀圖Instrument的桶狀結構:
from opentelemetry._metrics import get_meter_provider, set_meter_provider from opentelemetry.sdk._metrics import MeterProvider from opentelemetry.sdk._metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk._metrics.export import ConsoleMetricExporter from opentelemetry.sdk._metrics.aggregation import ExplicitBucketHistogramAggregation exporter = ConsoleMetricExporter() reader = PeriodicExportingMetricReader(exporter) provider = MeterProvider( metric_readers=[reader], views=[View( instrument_name="*”, aggregation=ExplicitBucketHistogramAggregation( (1,20,50,100,1000) ))], ) set_meter_provider(provider)
總結
在指標系列博文的第二部分,我們討論了OpenTelemetry標準,重點是它的六種Instrument型別:counters、asynchronous counters、UpDownCounters、asynchronous UpDownCounters、histograms和asynchronous gauges。
在該系列的最後一篇博文中,我們將介紹該模型與Prometheus的比較,解釋其中的差異,並分享我們關於選型的建議。
原文連結: A Deep Dive Into OpenTelemetry Metrics (翻譯:小灰灰)
- 深入理解Redis
- 深入OpenTelemetry指標
- 獨家新聞:Netflix歷史性地引入軟體工程師級別
- Cilium母公司宣佈完成4000萬美元B輪融資
- 深入理解四種Prometheus指標
- 對比 Istio,Linkerd 和 Consul
- 大規模分散式系統中的級聯故障
- 最佳軟體架構
- 服務網格的eBPF ? 是的,但 Envoy Proxy 將繼續存在
- eBPF、sidecars以及服務網格展望
- Apple M2裸片和架構分析--成本大幅增加及基於IP塊的 A15 晶片
- 如何擴充套件K8s API-K8s與Django
- 兩位將軍的問題
- Komodor 想讓每個工程師都成為 Kubernetes 專家
- 為什麼一次會議的成本比一臺MacBook Pro還高—減少會議中的開發人員的商業案例
- Go程式語言和環境
- Kubernetes聯合創始人Joe Beda:“軟體開發是一項團隊運動”
- 如何使用Go呼叫Kubernetes API-型別和普通機制
- Prodspec和Annealing
- 從零開始搭建SRE