六种 Instrument
| 类型 | 同步/异步 | 方向 | 用途 |
|---|---|---|---|
Counter | 同步 | 单调递增 | 请求数、字节数、error 数 |
UpDownCounter | 同步 | 可增可减 | 活跃连接数、队列长度 |
Histogram | 同步 | 分布 | 延迟、payload 大小 |
ObservableCounter | 异步(回调) | 单调 | 进程累计 CPU time |
ObservableUpDownCounter | 异步 | 可增可减 | goroutine 数、堆大小 |
ObservableGauge | 异步 | 瞬时值 | CPU 使用率、温度 |
记住一条原则:能算增量的用 Counter,只能读快照的用 Observable。
Counter:最常用的第一个
import { metrics } from "@opentelemetry/api"; const meter = metrics.getMeter("order-service"); const requestCounter = meter.createCounter("http.server.requests", { description: "HTTP requests count", unit: "{request}", // UCUM 单位 }); app.use((req, res, next) => { res.on("finish", () => { requestCounter.add(1, { "http.method": req.method, "http.route": req.route?.path ?? "unknown", "http.status_code": res.statusCode, }); }); next(); });
参数里那个对象叫 attributes(在 Prometheus 语境里等于 labels)——不同组合会生成不同的时间序列。
不要把
user.id、session.id、request.id 放进 metric 的 attributes。百万用户 = 百万条时间序列,存储会爆炸。高基数标识符只放在 trace 的 attributes 里。
Histogram:p50/p95/p99 的源头
const latency = meter.createHistogram("http.server.duration", { description: "HTTP request duration", unit: "ms", }); const start = Date.now(); await handle(req); latency.record(Date.now() - start, { "http.route": req.route.path, });
Histogram 不是返回原始值,而是把值分到桶里(如 [0-10ms, 10-50ms, 50-200ms ...])。后端从桶计算分位数。
Explicit Buckets vs Exponential Histogram
[10, 50, 100, 500, 1000]。简单,和 Prometheus 兼容,但需要预测数据范围。// 通过 View 把某个 metric 配成指数直方图 new MeterProvider({ views: [ new View({ instrumentName: "http.server.duration", aggregation: new ExponentialHistogramAggregation(), }), ], });
UpDownCounter:可增可减
const activeConnections = meter.createUpDownCounter( "db.client.connections.active", { unit: "{connection}" } ); pool.on("acquire", () => activeConnections.add(1)); pool.on("release", () => activeConnections.add(-1));
和 Counter 的区别只在可以减——Prometheus 里对应 Gauge 类型。
异步 Observable*
有些值不是"事件触发"而是"每隔一段时间读一次"——比如当前的堆内存、goroutine 数、温度。用 Observable,由 SDK 周期性调你的回调:
meter.createObservableGauge( "process.memory.heap_used", { unit: "By" }, (observer) => { const used = process.memoryUsage().heapUsed; observer.observe(used, { "process.pid": process.pid }); } ); // 每个 metric 读取周期(默认 60s),SDK 调一次回调
View:重塑指标
View 是 SDK 层的"规则"——在数据离开前可以改名、改聚合方式、加/减 attributes。
new MeterProvider({ views: [ // 1. 丢弃某些高基数 attribute new View({ instrumentName: "http.server.duration", attributeKeys: ["http.method", "http.status_code"], // 只保留这俩 }), // 2. 重命名 new View({ instrumentName: "old.name", name: "new.name", }), // 3. 换桶 new View({ instrumentName: "http.server.duration", aggregation: new ExplicitBucketHistogramAggregation( [10, 50, 100, 500, 1000, 5000] ), }), ], });
View 的威力在于运维和应用代码解耦——应用写 metric,运维调 view,不用改业务代码。
Delta vs Cumulative
rate() 做差。new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ temporalityPreference: AggregationTemporality.DELTA, }), exportIntervalMillis: 60000, });
选择原则:接 Prometheus 用 Cumulative,接 Datadog/AWS 用 Delta。Collector 也能在中间做 cumulativetodelta 转换。
单位:UCUM
OTel 用 UCUM(Unified Code for Units of Measure)标准单位:
| 含义 | UCUM 写法 |
|---|---|
| 毫秒 | ms |
| 秒 | s |
| 字节 | By |
| 千字节 | KiBy |
| 请求数(无量纲) | {request}(花括号包裹) |
| 百分比(0-1) | 1(乘法因子) |
和 Prometheus 兼容
Prometheus 有两种方式接 OTel:
prometheusreceiver 直接 scrape,原生 Prom 指标进入 OTel pipeline。适合逐步迁移。prometheusexporter 暴露 /metrics 端点给 Prometheus scrape。prometheusremotewriteexporter 直接把 OTel metrics 推给 Mimir/Thanos/Cortex。OTel 用
.(如 http.server.duration),Prometheus 用 _(如 http_server_duration_seconds)。Collector 会自动转换,你不用操心。
RED / USE 方法
埋点"埋什么"是门艺术,社区有两套经典方法论:
一段完整的 RED 埋点
const requests = meter.createCounter("http.server.requests"); const errors = meter.createCounter("http.server.errors"); const duration = meter.createHistogram("http.server.duration", { unit: "ms", }); app.use((req, res, next) => { const start = performance.now(); res.on("finish", () => { const attrs = { "http.method": req.method, "http.route": req.route?.path ?? "unknown", "http.status_code": res.statusCode, }; requests.add(1, attrs); if (res.statusCode >= 500) errors.add(1, attrs); duration.record(performance.now() - start, attrs); }); next(); });
这 20 行代码 + Grafana 一张图,就是 RED 全部——QPS、错误率、p99 一网打尽。
Exemplars:Metrics ↔ Traces 桥梁
Exemplar 是一种"指标里嵌 trace_id"的机制——你在 Grafana 看 p99 突刺,点一下刺尖的 exemplar,就直接跳到那一条慢 trace。
# Collector 配置 processors: metricsgeneration: exemplars: true # Prometheus 也要开 exemplar 支持 global: scrape_interval: 15s external_labels: send_exemplars: "true"
SDK 层只要同时开了 trace 和 metrics——当 metric 在 span 内被记录时,exemplar 会自动附带当前的 trace_id/span_id。
本章小结
- 6 种 instrument:Counter / UpDownCounter / Histogram 同步三件套,加三个 Observable 异步版
- attributes 决定时间序列——高基数标识符永远不进 metric
- Histogram 推荐用 Exponential(OTel 新能力)
- Cumulative(Prom 默认) vs Delta(Datadog 偏好),Collector 可互转
- View 让运维改 SDK 不改业务代码
- RED 方法:每个服务至少埋 requests/errors/duration 三个 metric
- Exemplars 把 metric 的一个点链回 trace