OpenTelemetry (OTEL) 指标,你是否曾认为只要调用 Counter.add(1) 就能神奇地积累数据? 🤔
事实上,我们在仪表盘上看到的每一个数字背后,都隐藏着4个看不见的核心阶段。
如果不理解这个完整的流程,当遇到“为什么这个指标是这样聚合的?”或者“为什么数据看起来是分离的?”之类的问题时,就很难找到解决的线索。
今天,我们将深入剖析OpenTelemetry指标数据从开发者编写代码的第一刻起,到数据最终记录到存储中的4个阶段。读完这篇文章,你将成为OTEL指标的专家! ✨

阶段1:用什么工具测量? – Instrument Model 🛠️
所有测量的开始都源于决定“使用什么工具”。Instrument Model 指的就是生成数据的工具(API)本身。
您必须根据数据的性质选择合适的工具,例如它是只增加的值,还是会上下波动的值。
- 说明: 它是6种核心仪表(Counter、UpDownCounter、Histogram等)的API规范,也是数据采集的第一个关卡。开发者通过此模型定义要创建“哪种类型”的指标。
- 核心问题:
- “这个数据只增加吗?” ➡️ 选择 Counter!
- “它代表当前状态值(例如:CPU使用率)吗?” ➡️ 选择 Observable Gauge!
这个阶段就像在实际数据产生之前绘制蓝图。它与实际测量值“事件模型”或聚合数据管道“指标流”有明确区别。它仅仅是“声明”要使用哪种工具的阶段。
// 示例:使用 Go 语言创建名为 "http.requests" 的 Counter“工具”
meter := provider.GetMeter("my-app")
httpRequestsCounter, _ := meter.Int64Counter("http.requests")
阶段2:数据的诞生,首次测量值 – Event Model 👶
选择了工具之后,现在是时候进行实际测量了。Event Model 指的是聚合(Aggregation)之前的每一个原始测量(Raw Measurement)事件。
- 说明: 这是输入到OTEL指标模型中最纯粹、最基本的数据。它发生在我们代码中调用API的那一刻。
- 示例:
- 调用 httpRequestsCounter.Add(ctx, 1) 的行为本身
- 使用 histogram.Record(ctx, 52.4) 记录响应时间的单个测量值
这是尚未经过任何加工的“原始”数据。它表示在特定时间、具有特定属性的值被“测量一次”的事实本身。这些事件汇聚起来,形成下一个阶段——“指标流”。
阶段3:构建数据高速公路 – Metric Stream 🛣️
当产生大量原始测量值(Event)时,OTEL SDK 不会随意混合它们。它会将具有独特特征的数据捆绑在一起,创建独立的“逻辑管道”,这就是 Metric Stream。它是OTEL内部识别和处理数据最重要的核心单元。
- 说明: 它是通过资源(Resource)+ 仪表(Instrument)+ 属性(Attributes)的独特组合来识别的逻辑数据流。SDK 以此流为单位聚合和处理数据。
- 核心: 如果属性(Attributes)不同,即使是相同的仪表,也会被视为完全不同的流。
为什么这个概念很重要?我们来看下面的例子。
// 使用相同的 http.requests Counter,但是...
// 仪表: "http.requests"
// 事件 1
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "GET"), attribute.Int("status", 200))
// 事件 2
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "POST"), attribute.Int("status", 500))
// 事件 3
httpRequestsCounter.Add(ctx, 1, attribute.String("method", "GET"), attribute.Int("status", 200))
在上面的代码中,Event 1 和 Event 3 具有相同的属性 {method=”GET”, status=”200″}。因此,它们都属于一个指标流。
另一方面,Event 2 具有不同的属性 {method=”POST”, status=”500″},因此它被视为完全独立的指标流。
- 流 1: http.requests + {method=”GET”, status=”200″}
- 流 2: http.requests + {method=”POST”, status=”500″}
因此,指标流是一个中间管道,用于将数据分类并聚合为有意义的单元,它与最终产物“时间序列模型”不同。时间序列是经过此管道产生的“结果”。
阶段4:最终报告,传输到后端 – Timeseries Model 📊
所有聚合和处理完成后,我们终于可以看到在 Prometheus 或 Datadog 等后端系统中显示的最终数据格式。这就是 Timeseries Model。
- 说明: 这是 OTEL SDK 在完成每个指标流的聚合、时间性(Temporality)处理等所有操作后,向外部导出的最终产物。
- 构成: 它由(指标名称 + 属性 + 数据点[时间+值])的组合构成。
让我们看看上面的指标流示例最终会变成什么样的时间序列数据。
Raw Data (Timeseries Payload)
{
"resourceMetrics": [
{
"scopeMetrics": [
{
"metrics": [
{
"name": "http.requests",
"sum": {
"dataPoints": [
{
"attributes": [
{ "key": "method", "value": { "stringValue": "GET" } },
{ "key": "status", "value": { "intValue": "200" } }
],
"startTimeUnixNano": "1762489800000000000",
"timeUnixNano": "1762489860000000000",
"asInt": "2" // 事件 1 和 3 合并的结果
},
{
"attributes": [
{ "key": "method", "value": { "stringValue": "POST" } },
{ "key": "status", "value": { "intValue": "500" } }
],
"startTimeUnixNano": "1762489800000000000",
"timeUnixNano": "1762489860000000000",
"asInt": "1" // 事件 2 的结果
}
],
"aggregationTemporality": "CUMULATIVE",
"isMonotonic": true
}
}
]
}
]
}
]
}
在 Prometheus 等系统中,这些数据会转换为以下格式显示:
http_requests_total{method="GET", status="200"} 2
http_requests_total{method="POST", status="500"} 1
这就是我们在仪表盘上看到的最终数据形式!
🗺️ 整体流程一览
那么,让我们从头到尾总结一下整个过程吧!
- Instrument Model (工具选择 🛠️): 开发者选择一个名为 Counter 的工具,并说:“我要统计请求数量!”
- Event Model (原始测量值产生 👶): 每当用户请求到来时,都会调用 counter.add(1),并伴随属性({method=”GET”})产生原始测量值。
- Metric Stream (逻辑管道处理 🛣️): SDK 根据属性区分这些测量值。{method=”GET”} 请求被发送到 GET 流,{method=”POST”} 请求被发送到 POST 流,并在各自的逻辑管道中开始聚合(求和)。
- Timeseries Model (最终产物传输 📊): 经过一定时间聚合的结果,例如 http_requests_total{method=”GET”} = 120,成为时间序列数据(最终产物)并传输到监控后端。
现在您是否完全理解了 OpenTelemetry 指标是如何工作的?如果您记住这4个阶段的流程,将来无论遇到任何与指标相关的问题,都将能够自信地分析和解决!
发表回复