OpenTelemetry 入门:架构、数据采集器、导出器、采样策略
聊到平台工程的可观测性,OpenTelemetry 基本上是绕不开的。我刚开始接触它的时候,也觉得这东西有点“重”,又是 SDK 又是 Collector 的。但用久了你会发现,它其实帮你解决了一个最头疼的问题——数据格式的碎片化。说白了,它就是可观测性领域的“通用语言”。
咱们这一章,不扯太虚的理论。我就带你看看 OpenTelemetry 到底怎么组织数据的,它的核心部件长什么样,以及你落地时最该注意的几个坑。
架构:数据是怎么流动的?
OpenTelemetry 的架构其实不复杂。你可以把它想象成一个“数据管道”模型,总共就四个角色:应用、SDK、收集器和后端。
先看这张图,你就明白了:
你想想看,这个链路里最关键的角色其实是 Collector。它不只是一个“转发”的中间件。我个人习惯把 Collector 当作一个“智能数据路由器”。它能在数据到达后端之前,帮你做预处理、采样甚至脱敏。
数据采集器:不止是 Agent
很多人一听到“采集器”就以为是跑个 Agent 就完事了。其实在 OpenTelemetry 里,采集数据有两条路:库内采集和边车采集。
库内采集(Direct Instrumentation)
这种方式是在你的应用进程里,通过 SDK 直接生成数据。好处是延迟低,能拿到最细的上下文。我以前在重构一个 Go 微服务时,就用了这种方式。代码里简单加几行,就能看到每次 RPC 调用的内部耗时,很直观。
- 优点:数据实时性好,上下文完整
- 缺点:对代码有侵入性,版本升级需要跟着应用走
边车采集(Sidecar / Proxy)
这种方式适合你动不了代码的场景。比如你用的是别人维护的旧系统,或者中间件。这时候可以用 Collector 作为 Sidecar,通过监听端口或日志文件来采集数据。嗯,这里要注意:这种方式拿不到应用内部的方法调用链,只能拿到网络层面的数据。
- 优点:零代码改动,适合遗留系统
- 缺点:数据维度有限,上下文丢失严重
实战建议:我建议你,新项目用库内采集,老项目实在改不动了再用边车方式。别上来就全上 Sidecar,那样你排查问题时会发现数据“缺胳膊少腿”。
导出器:数据通往哪?
数据采集完了,得送出去吧?导出器就是干这个的。OpenTelemetry 的导出器设计得很灵活,它支持“推模式”也支持“拉模式”。
常见导出器对比
| 导出器类型 | 协议/方式 | 典型后端 | 适用场景 |
|---|---|---|---|
| OTLP Exporter | gRPC / HTTP | Collector, Jaeger, 自研 | 通用,首选 |
| Prometheus Exporter | HTTP 拉取 | Prometheus, VictoriaMetrics | 指标数据(Metrics) |
| Zipkin Exporter | HTTP | Zipkin | 老系统对接 |
| Console / File Exporter | Stdout / 文件 | 调试日志 | 开发调试阶段 |
避坑指南:我曾经直接把 OTLP 数据发到 Prometheus,结果发现 Trace 数据全丢了。因为 Prometheus 只认 Metrics 格式。记住,导出器要匹配后端的类型,Trace 就走 OTLP,Metrics 才走 Prometheus 接口。
采样策略:数据洪流下的“过滤器”
说到采样,这可能是 OpenTelemetry 里最容易被低估的配置了。你想想看,一个高并发的系统,每秒可能产生几万条 Span。全量存?成本直接炸裂。不存?问题又查不到。所以采样策略一定要搞清楚。
OpenTelemetry 支持两种采样位置:SDK 端采样和Collector 端采样。我个人建议,能用 SDK 端采样的尽量在 SDK 端解决,减少网络传输。
常见的采样策略
- 概率采样(Probabilistic Sampling):按固定比例采样,比如 10%。适合大盘流量你只需要大概趋势的场景。
- 尾采样(Tail-based Sampling):等整个请求结束再判断是否采样。这招特别适合“查错”场景,因为它能保证错误链路被完整保留。
- 头采样(Head-based Sampling):在请求开始时决定是否采样。速度快,但可能错过后面才发生的错误。
我一般怎么搭配呢?对于日常监控,用概率采样就够了,1%~10% 的比例基本能反映全貌。但对于线上故障排查,我会在 Collector 里配一个尾采样规则:只要 Span 状态码是 ERROR,或者延迟超过 500ms,就强制采样。
来看看一个 SDK 端概率采样的配置(以 Go 为例):
// 设置采样率为 10%
tp, err := sdktrace.NewProvider(
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))),
sdktrace.WithBatcher(exporter),
)
if err != nil {
log.Fatal(err)
}
这段代码很简单,但我遇到过一个大坑。曾经有个同事把 0.1 写成了 1.0,结果全量采样,一夜之间把后端存储打满了。嗯,这种低级错误,代码 Review 时一定要盯紧了。
警告:不要在生产环境直接使用 <= 0.5 的高采样率。先评估你的存储容量。我见过一个团队上线前忘了关调试模式的 100% 采样,三天后存储告警直接爆了。
采样策略决策表格
| 场景 | 推荐策略 | 位置 | 备注 |
|---|---|---|---|
| 大流量服务(>10k req/s) | 概率采样 1%~10% | SDK | 控制成本,保留趋势 |
| 核心交易链路 | 尾采样 + 错误强制保留 | Collector | 查错必备,延迟可接受 |
| 开发/测试环境 | 全量采样 100% | SDK | 方便调试,无成本压力 |
| API Gateway / 边缘网关 | 头采样 + 关键路径固定保留 | SDK/Collector | 低延迟,重点监控 |
最后我得再啰嗦一句:采样策略不是静态的。业务高峰期、上线新版本时,可以临时调高采样率。我习惯把它配成动态配置,通过 ConfigMap 推送,这样不用重启服务就能改。