如果说可观测性是一座大厦,三大信号是建材,那么 OpenTelemetry 就是建筑标准——它定义了信号如何产生、如何传播、如何采集、如何导出。没有 OTel,每个团队用不同的 SDK、不同的格式、不同的 Collector,信号之间无法关联;有了 OTel,所有信号遵循统一的 API 和协议,跨服务、跨语言、跨平台无缝协作。
OpenTelemetry 不是又一个可观测性工具,而是可观测性的TCP/IP——它不关心你用什么存储后端,只关心数据如何产生和传输。
一、OpenTelemetry 的定位
1.1 从碎片化到标准化
在 OTel 出现之前,可观测性 SDK 是碎片化的:
| 信号 | 常见 SDK | 问题 |
|---|---|---|
| 追踪 | Jaeger SDK、Zipkin SDK、AWS X-Ray SDK | 格式不兼容,传播协议不同 |
| 指标 | Prometheus SDK、StatsD SDK、DogStatsD SDK | 数据模型不同,导出格式不同 |
| 日志 | 各语言自带日志库 | 无标准结构化格式 |
OTel 统一了三大信号的 API 和 SDK,让应用代码与后端实现解耦:
1.2 OTel 的核心价值
| 价值 | 说明 |
|---|---|
| 厂商中立 | 应用代码不绑定任何后端 |
| 统一 API | 追踪、指标、日志共享同一套 API |
| 自动仪器化 | 无需修改业务代码即可产生信号 |
| 语义约定 | 标准化的属性名称,确保跨服务一致性 |
| OTLP 协议 | 统一的数据传输协议 |
| Collector | 统一的数据采集、处理、路由 |
二、OTel 三层架构
2.1 API → SDK → Collector
2.2 API 层
API 层定义了接口,应用代码只依赖 API,不依赖具体实现:
import ( "go.opentelemetry.io/otel/trace" // Tracer API "go.opentelemetry.io/otel/metric" // Meter API "go.opentelemetry.io/otel/log" // Logger API(稳定化中))
// 应用代码只使用 API,不关心后端实现func processOrder(ctx context.Context, order Order) error { // 追踪 API ctx, span := otel.Tracer("order-service").Start(ctx, "ProcessOrder") defer span.End()
// 指标 API requestCounter.Add(ctx, 1, metric.WithAttributes(semconv.HTTPMethodKey.String("POST")))
// 日志 API logger.Info(ctx, "Order processed", log.WithAttributes(log.String("order_id", order.ID)))
return nil}2.3 SDK 层
SDK 层实现了 API 接口,负责数据采集、处理和导出:
import ( "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc")
func initOTel(ctx context.Context) (func(), error) { // 创建 OTLP Exporter traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint("otel-collector:4317"), otlptracegrpc.WithInsecure(), )
metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithEndpoint("otel-collector:4317"), otlpmetricgrpc.WithInsecure(), )
// 创建 TracerProvider tp := trace.NewTracerProvider( trace.WithBatcher(traceExporter, trace.WithBatchTimeout(5*time.Second), trace.WithMaxExportBatchSize(512), ), trace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("order-service"), semconv.ServiceVersionKey.String("v2.3.1"), )), ) otel.SetTracerProvider(tp)
// 创建 MeterProvider mp, err := metric.NewMeterProvider( metric.WithReader(metric.NewPeriodicReader(metricExporter, metric.WithInterval(30*time.Second), )), ) otel.SetMeterProvider(mp)
return func() { tp.Shutdown(ctx) mp.Shutdown(ctx) }, nil}2.4 SpanProcessor 管道
SpanProcessor 是 SDK 层的核心扩展点,定义了 Span 的处理管道:
| SpanProcessor | 功能 | 适用场景 |
|---|---|---|
BatchSpanProcessor | 批量导出,减少网络开销 | 生产环境(推荐) |
SimpleSpanProcessor | 同步逐条导出 | 调试/开发环境 |
FilterSpanProcessor | 过滤不需要的 Span | 移除健康检查 Span |
MultiSpanProcessor | 组合多个 Processor | 复杂处理管道 |
三、OTel Collector
3.1 Collector 架构
OTel Collector 是可观测性数据管道的核心组件,采用 Receiver → Processor → Exporter 的管道架构:
3.2 Collector 部署模式
| 模式 | 架构 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Sidecar | 每个 Pod 一个 Collector | 隔离性好 | 资源开销大 | 安全要求高 |
| DaemonSet | 每个节点一个 Collector | 资源效率高 | 单点故障风险 | Kubernetes 生产环境 |
| Gateway | 集中式 Collector | 易管理、可扩展 | 网络跳数多 | 大规模集群 |
# Kubernetes DaemonSet 部署apiVersion: apps/v1kind: DaemonSetmetadata: name: otel-collector-agentspec: selector: matchLabels: app: otel-collector template: spec: containers: - name: otel-collector image: otel/opentelemetry-collector-contrib:0.96.0 command: ["--config=/etc/otelcol/config.yaml"] volumeMounts: - name: config mountPath: /etc/otelcol resources: limits: memory: 512Mi cpu: 200m requests: memory: 256Mi cpu: 100m volumes: - name: config configMap: name: otel-collector-config3.3 Collector 配置实战
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 max_recv_msg_size_mib: 16 http: endpoint: 0.0.0.0:4318
processors: # 批量处理:减少网络请求次数 batch: send_batch_size: 1024 send_batch_max_size: 2048 timeout: 5s
# K8s 属性注入:自动添加 Pod/Deployment 信息 k8sattributes: extract: metadata: - k8s.namespace.name - k8s.deployment.name - k8s.pod.name
# 过滤:移除健康检查 Span filter: error_mode: ignore traces: span: - 'attributes["http.route"] == "/healthz"' - 'attributes["http.route"] == "/readyz"'
# 内存限制:防止 OOM memory_limiter: check_interval: 5s limit_mib: 400 spike_limit_mib: 100
exporters: # 追踪 → Tempo otlphttp/traces: endpoint: http://tempo:4318
# 指标 → Prometheus prometheus: endpoint: 0.0.0.0:8889 namespace: otel
# 日志 → Loki loki: endpoint: http://loki:3100/loki/api/v1/push default_labels_enabled: exporter: false
service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, k8sattributes, filter, batch] exporters: [otlphttp/traces] metrics: receivers: [otlp] processors: [memory_limiter, k8sattributes, batch] exporters: [prometheus] logs: receivers: [otlp] processors: [memory_limiter, k8sattributes, batch] exporters: [loki]
# 健康检查和监控 extensions: [health_check, pprof, zpages]
extensions: health_check: endpoint: 0.0.0.0:13133 pprof: endpoint: 0.0.0.0:1777 zpages: endpoint: 0.0.0.0:55679四、自动仪器化
4.1 原理
自动仪器化(Auto-Instrumentation)通过拦截框架/库的调用,自动产生追踪、指标和日志,无需修改业务代码:
4.2 Go 自动仪器化
# 使用 otel-go 自动仪器化go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttpgo get go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpcgo get go.opentelemetry.io/contrib/instrumentation/runtime
# HTTP 自动仪器化import ( "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp")
func main() { // 包装 HTTP Handler,自动创建 Span handler := otelhttp.NewHandler( http.HandlerFunc(handleOrder), "order-handler", otelhttp.WithMessageEvents(otelhttp.Read, otelhttp.Write), ) http.Handle("/api/v1/orders", handler) http.ListenAndServe(":8080", nil)}4.3 Java 自动仪器化
# 使用 OTel Java Agent(零代码修改)java -javaagent:opentelemetry-javaagent.jar \ -Dotel.service.name=order-service \ -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \ -Dotel.instrumentation.common.enabled=true \ -jar my-app.jar4.4 Python 自动仪器化
# 使用 OTel Python 自动仪器化from opentelemetry.instrumentation.auto_instrumentation import sitecustomize
# 或手动初始化from opentelemetry.instrumentation.flask import FlaskInstrumentorfrom opentelemetry.instrumentation.requests import RequestsInstrumentorfrom opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
FlaskInstrumentor().instrument()RequestsInstrumentor().instrument()SQLAlchemyInstrumentor().instrument()4.5 各语言自动仪器化支持
| 语言 | 方式 | 覆盖范围 | 性能开销 |
|---|---|---|---|
| Java | Java Agent | HTTP、gRPC、JDBC、Kafka 等 | 5-15% |
| Python | 装饰器/自动 | Flask、Django、requests 等 | 5-10% |
| Go | 包装函数 | net/http、gRPC、database/sql | 3-8% |
| Node.js | 模块补丁 | Express、HTTP、MongoDB 等 | 5-10% |
| .NET | CLR Profiler | ASP.NET、HttpClient、SQL | 5-15% |
自动仪器化的性能开销通常在 3-15%,对于大多数生产环境是可接受的。但如果你的服务对延迟极度敏感(如交易系统),建议只仪器化关键路径,而非全量自动仪器化。
五、OTLP 协议
5.1 OTLP 设计
OTLP(OpenTelemetry Protocol)是 OTel 定义的统一数据传输协议,支持 gRPC 和 HTTP 两种传输方式:
| 维度 | OTLP/gRPC | OTLP/HTTP |
|---|---|---|
| 传输 | gRPC (HTTP/2) | HTTP/1.1 or HTTP/2 |
| 编码 | Protobuf 二进制 | Protobuf 二进制 or JSON |
| 性能 | 高(二进制 + 多路复用) | 中 |
| 兼容性 | 需要 gRPC 支持 | 任何 HTTP 客户端 |
| 推荐 | 服务间通信 | 浏览器/边缘场景 |
5.2 OTLP 数据模型
// 简化的 OTLP 数据模型message ExportTraceServiceRequest { repeated ResourceSpans resource_spans = 1;}
message ResourceSpans { Resource resource = 1; repeated ScopeSpans scope_spans = 2;}
message ScopeSpans { InstrumentationScope scope = 1; repeated Span spans = 2;}
message Span { string trace_id = 1; string span_id = 2; string parent_span_id = 3; string name = 4; SpanKind kind = 5; fixed64 start_time_unix_nano = 6; fixed64 end_time_unix_nano = 7; repeated KeyValue attributes = 8; SpanStatus status = 9; repeated Event events = 10; repeated Link links = 11;}六、语义约定
6.1 为什么需要语义约定?
语义约定(Semantic Conventions)定义了标准化的属性名称和值,确保跨服务、跨语言的一致性:
// 使用语义约定span.SetAttributes( semconv.HTTPMethodKey.String("GET"), semconv.HTTPStatusCodeKey.Int(200), semconv.HTTPRouteKey.String("/api/v1/orders"), semconv.DBSystemKey.String("postgresql"), semconv.DBOperationKey.String("SELECT"),)
// 自定义属性名(不一致)span.SetAttributes( attribute.String("method", "GET"), attribute.Int("status", 200), attribute.String("route", "/api/v1/orders"), attribute.String("db", "pg"), attribute.String("op", "select"),)6.2 核心语义约定
| 领域 | 属性 | 说明 |
|---|---|---|
| HTTP | http.method | 请求方法 |
| HTTP | http.status_code | 响应状态码 |
| HTTP | http.route | 路由模板(非完整 URL) |
| HTTP | http.url | 完整 URL |
| Database | db.system | 数据库类型 |
| Database | db.operation | 操作类型 |
| Database | db.statement | SQL 语句 |
| Messaging | messaging.system | 消息系统 |
| Messaging | messaging.destination | 目标主题/队列 |
| RPC | rpc.system | RPC 系统 |
| RPC | rpc.method | RPC 方法 |
| Resource | service.name | 服务名称 |
| Resource | service.version | 服务版本 |
七、动手实践:搭建 OTel 采集管道
7.1 部署 OTel Collector
docker compose up -d otel-collector
# 验证 Collector 运行curl http://localhost:13133/7.2 验证数据流
# 发送追踪 → Collector → Tempocurl -X POST http://localhost:4318/v1/traces \ -H "Content-Type: application/json" \ -d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test"}}]},"scopeSpans":[{"spans":[{"traceId":"01234567890123456789012345678901","spanId":"0123456789012345","name":"test","kind":1,"startTimeUnixNano":"1700000000000000000","endTimeUnixNano":"1700000001000000000"}]}]}]}'
# 发送指标 → Collector → Prometheuscurl -X POST http://localhost:4318/v1/metrics \ -H "Content-Type: application/json" \ -d '{"resourceMetrics":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test"}}]},"scopeMetrics":[{"metrics":[{"name":"test.counter","gauge":{"dataPoints":[{"asDouble":1,"timeUnixNano":"1700000000000000000"}]}}]}]}]}'
# 发送日志 → Collector → Lokicurl -X POST http://localhost:4318/v1/logs \ -H "Content-Type: application/json" \ -d '{"resourceLogs":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test"}}]},"scopeLogs":[{"logRecords":[{"timeUnixNano":"1700000000000000000","severityNumber":9,"body":{"stringValue":"Test log message"}}]}]}]}'7.3 验证清单
| 检查项 | 验证方式 | 预期结果 |
|---|---|---|
| Collector 健康检查 | curl http://localhost:13133/ | HTTP 200 |
| OTLP gRPC 接收 | 发送追踪到 4317 | Tempo 中能看到 |
| OTLP HTTP 接收 | 发送追踪到 4318 | Tempo 中能看到 |
| 三管道独立 | 分别发送追踪/指标/日志 | 各后端都能收到 |
| Batch Processor | 检查 Collector 指标 | 批量大小符合预期 |
OTel Collector 的部署模式直接影响可观测性管道的可靠性。推荐使用 DaemonSet 模式(每个节点一个 Collector)作为 Agent,再加一个 Deployment 模式的 Gateway Collector 做集中处理。这种两层架构既保证了数据采集的可靠性,又避免了 Agent 直接与后端耦合。
八、本章小结
上一章深入探讨了分布式追踪与 Span 模型。 可观测性的统一标准——OpenTelemetry的要点基本都在这里了。
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| 三层架构 | API(接口)→ SDK(实现)→ Collector(采集路由),应用代码只依赖 API。 | 三层架构 |
| Collector | Receiver → Processor → Exporter 管道架构,是可观测性数据的中央枢纽。 | Collector |
| 自动仪器化 | 无需修改业务代码即可产生信号,Java Agent 最成熟,Go 需要包装函数。 | 自动仪器化 |
| OTLP 协议 | 统一的二进制传输协议,支持 gRPC 和 HTTP。 | OTLP 协议 |
| 语义约定 | 标准化的属性名称,确保跨服务一致性。 | 语义约定 |
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






