從 Prometheus 到 OpenTelemetry:指標監(jiān)控的演進與實踐
背景
關于 metrics 我最早接觸相關概念的就是 prometheus,它是第二個加入 CNCF(云原生)社區(qū)的項目(第一個是 kubernetes),可見在云原生領域 Metrics 指標監(jiān)控從誕生之初就是一個非常重要的組件。
現(xiàn)實也確實如此,如今只要使用到了 kubernetes 相關的項目,對其監(jiān)控就是必不可少的。
當然也不止是云原生的項目才需要 Metrics 指標監(jiān)控,我們任何一個業(yè)務都是需要的,不然我們的服務運行對開發(fā)運維來說都是一個黑盒,無法知道此時系統(tǒng)的運行情況,因此才需要我們的業(yè)務系統(tǒng)將一些關鍵運行指標暴露出來。
圖片
業(yè)務數(shù)據(jù):比如訂單的增長率、銷售金額等業(yè)務數(shù)據(jù);同時還有應用自身的資源占用情況:
- QPS
- Latency
- 內存
- CPU 等信息。
在使用 OpenTelemetry 之前,因為 prometheus 是這部分的絕對標準,所以我們通常都會使用 prometheus 的包來暴露這些指標:
<!-- The client -->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
<version>0.16.0</version>
</dependency>
<!-- Hotspot JVM metrics-->
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_hotspot</artifactId>
<version>0.16.0</version>
</dependency>
暴露一個自定義的指標也很簡單:
import io.prometheus.client.Counter;
class YourClass {
static final Counter requests = Counter.build()
.name("requests_total").help("Total requests.").register();
void processRequest() {
requests.inc();
// Your code here.
}
}
這是暴露一個單調遞增的指標,prometheus 還提供了其他幾種指標類型:
- Counter
- Gauge
- Histogram
之后我們只需要在 prometheus 中配置一些抓取規(guī)則即可:
scrape_configs:
- job_name: 'springboot'
scrape_interval: 10s
static_configs:
- targets: ['localhost:8080'] # Spring Boot ip+port
當然如果是運行在 kubernetes 環(huán)境,prometheus 也可以基于服務發(fā)現(xiàn)配置一些規(guī)則,自動抓取我們的 Pod 的數(shù)據(jù),由于不是本文的重點就不過多介紹。
基本組件
在 OpenTelemetry 中自然也提供了 Metrics 這個組件,同時它也是完全兼容 Prometheus,所以我們理解和使用起來并不復雜。
MeterProvider
不同于 prometheus 客戶端中直接提供了 Counter 就可以創(chuàng)建指標了,在 OpenTelemetry 中會提供一個 MeterProvider 的接口,使用這個接口可以獲取 Meter,再使用 Meter 才可以創(chuàng)建 Counter、Gauge、Histogram 等數(shù)據(jù)。
下面來看看具體如何使用,這里我以 Pulsar 源碼的代碼進行演示:
public InstrumentProvider(OpenTelemetry otel) {
if (otel == null) {
// By default, metrics are disabled, unless the OTel java agent is configured.
// This allows to enable metrics without any code change. otel = GlobalOpenTelemetry.get();
} this.meter = otel.getMeterProvider()
.meterBuilder("org.apache.pulsar.client")
.setInstrumentationVersion(PulsarVersion.getVersion())
.build();
}
LongCounterBuilder builder = meter.counterBuilder(name)
.setDescription(description)
.setUnit(unit.toString());
Meter Exporter
Meter Exporter 則是一個 OpenTelemetry 獨有的概念,與我們之前講到的一樣:OpenTelemetry 作為廠商無關的平臺,允許我們將數(shù)據(jù)寫入到任何兼容的產品里。
所以我們在使用 Metrics 時需要指定一個 exporter:
Exporter 類型 | 作用 | 備注 | 參數(shù) |
OTLP Exporter | 通過 OpenTelemetry Protocol(OTLP) 發(fā)送指標數(shù)據(jù)到 collect。 | 默認生產環(huán)境中推薦使用,需要將數(shù)據(jù)發(fā)送到支持 OTLP 的后端,如 OpenTelemetry Collector。 | -Dotel.metrics.exporter=otlp (default) |
Console Exporter | 將指標數(shù)據(jù)打印到控制臺的導出器。 | 開發(fā)和調試,快速查看指標數(shù)據(jù)。 | -Dotel.metrics.exporter=console |
Prometheus Exporter | 將指標數(shù)據(jù)以 Prometheus 抓取的格式暴露給 Prometheus 服務。 | 與 Prometheus 集成,適用于需要 Prometheus 監(jiān)控的場景,這個可以無縫和以往使用 prometheus 的場景兼容 | -Dotel.metrics.exporter=prometheus |
Metric Instruments
與 prometheus 類似,OpenTelemetry 也提供了以下幾種指標類型:
- Counter:單調遞增計數(shù)器,比如可以用來記錄訂單數(shù)、總的請求數(shù)。
- UpDownCounter:與 Counter 類似,只不過它可以遞減。
- Gauge:用于記錄隨時在變化的值,比如內存使用量、CPU 使用量等。
- Histogram:通常用于記錄請求延遲、響應時間等。
同時每個指標還有以下幾個字段:
- Name:名稱,必填。
- Kind:類型,必填。
- Unit:單位,可選。
- Description:描述,可選。
messageInCounter = meter
.counterBuilder(MESSAGE_IN_COUNTER)
.setUnit("{message}")
.setDescription("The total number of messages received for this topic.")
.buildObserver();
還是以 Pulsar 的為例,messageInCounter 是一個記錄總的消息接收數(shù)量的 Counter 類型。
subscriptionCounter = meter
.upDownCounterBuilder(SUBSCRIPTION_COUNTER)
.setUnit("{subscription}")
.setDescription("The number of Pulsar subscriptions of the topic served by this broker.")
.buildObserver();
這是記錄一個訂閱者數(shù)量的指標,類型是 UpDownCounter,也就是可以增加減少的指標。
private static final List<Double> latencyHistogramBuckets =
Lists.newArrayList(.0005, .001, .0025, .005, .01, .025, .05, .1, .25, .5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0);
DoubleHistogramBuilder builder = meter.histogramBuilder("pulsar.client.producer.message.send.duration")
.setDescription("Publish latency experienced by the application, includes client batching time")
.setUnit(Unit.Seconds.toString())
.setExplicitBucketBoundariesAdvice(latencyHistogramBuckets);
這是一個記錄 Pulsar producer 發(fā)送延遲的指標,類型是 Histogram。
backlogQuotaAge = meter
.gaugeBuilder(BACKLOG_QUOTA_AGE)
.ofLongs()
.setUnit("s")
.setDescription("The age of the oldest unacknowledged message (backlog).")
.buildObserver();
這是一個記錄最大 unack 也就是 backlog 時間的指標,類型是 Gauge。
案例
在之前的文章:實戰(zhàn):如何編寫一個 OpenTelemetry Extensions中講過如何開發(fā)一個 OpenTelemetry 的 extension,其實當時我就是開發(fā)了一個用于在 Pulsar 客戶端中暴露指標的一個插件。
不過目前 Pulsar 社區(qū)已經集成了該功能。
其中的核心代碼與上面講到的類似:
public static void registerObservers() {
Meter meter = MetricsRegistration.getMeter();
meter.gaugeBuilder("pulsar_producer_num_msg_send")
.setDescription("The number of messages published in the last interval")
.ofLongs()
.buildWithCallback(
r -> recordProducerMetrics(r, ProducerStats::getNumMsgsSent));
private static void recordProducerMetrics(ObservableLongMeasurement observableLongMeasurement, Function<ProducerStats, Long> getter) {
for (Producer producer : CollectionHelper.PRODUCER_COLLECTION.list()) {
ProducerStats stats = producer.getStats();
String topic = producer.getTopic();
if (topic.endsWith(RetryMessageUtil.RETRY_GROUP_TOPIC_SUFFIX)) {
continue;
} observableLongMeasurement.record(getter.apply(stats),
Attributes.of(PRODUCER_NAME, producer.getProducerName(), TOPIC, topic));
}}
只是這里使用了 buildWithCallback 回調函數(shù),OpenTelemetry 會每隔 30s 調用一次這個函數(shù),通常適用于 Gauge 類型的數(shù)據(jù)。
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.javaagent.extensinotallow=ext.jar \
-Dotel.metrics.exporter=prometheus \
-Dotel.exporter.prometheus.port=18180 \
-jar myapp.jar
配合上 Prometheus 的兩個啟動參數(shù)就可以在本地 18180 中獲取到指標數(shù)據(jù):
curl http://127.0.0.1:18180/metrics
當然也可以直接發(fā)往 OpenTelemetry-Collector 中,再由它發(fā)往 prometheus,只是這樣需要額外在 collector 中配置一下:
exporters:
debug: {}
otlphttp:
metrics_endpoint: http://promethus:8480/insert/0/opentelemetry/api/v1/push
service:
pipelines:
metrics:
exporters:
- otlphttp
processors:
- k8sattributes
- batch
receivers:
- otlp
圖片
這樣我們就可以在 Grafana 中通過 prometheus 查詢到數(shù)據(jù)了。
有一點需要注意,如果我們自定義的指標最好是參考官方的語義和命名規(guī)范來定義這些指標名稱。
圖片
比如 OpenTelemetry 的規(guī)范中名稱是用 . 來進行分隔的。
切換為 OpenTelemetry 之后自然就不需要依賴 prometheus 的包,取而代之的是 OTel 的包:
compileOnly 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.34.1'
compileOnly 'io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:1.32.0'
總結
相對來說 Metrics 的使用比 Trace 簡單的多,同時 Metrics 其實也可以和 Trace 進行關聯(lián),也就是 Exemplars,限于篇幅就不在本文展開了,感興趣的可以自行查閱。
參考鏈接: