现在的业务系统具有很高的复杂性,为了能够追踪请求的完整流程,通常会实现一种叫做分布式追踪(Distributed Tracing)的机制。
而接入"分布式追踪"的方式或者说标准有很多, 比如:OpenTracing 、Zipkin 、SkyWalking等等。 本文的内容是基于"新星" - OpenTelemetry的。
选择OpenTelemetry的原因是:
- 它由
CNCF
托管, 并且提供了一套统一的 API,用于追踪和度量,这意味着开发者可以编写一次代码,然后将其导出到多个后端系统,而无需针对每个系统进行特定的适配。 - 支持多种编程语言,设计上支持扩展,允许开发者添加自定义的追踪器、度量收集器和导出器。
- 也支持接收Zipkin、Jaeger、Prometheus等格式的数据,拥有很高的集成度。
分布式追踪是可观测性的一个重要方面,而可观测性是一个更全面的概念,它结合了日志、度量和追踪来提供一个全面的系统视图,帮助团队确保系统可靠、高效和可维护。
OpenTelemetry 是 OpenTracing 和 OpenCensus 的继承者,旨在提供一种统一的框架和标准,用于观测性数据的收集、转换和导出。
它提供了一系列的 API 和 SDK,用于自动收集应用程序的指标(Metrics)、日志(Logs)和追踪(Traces)数据。
与服务网格的关系
个人认为服务网格与可观测是相辅相成的,但不是必须的。
我们的环境是存在服务网格的, 并且大多数应用都在网格中。 先抛开网格的治理能力, 在可观测的场景下,网格也为丰富数据做出了很多贡献~
OpenTelemetry基本架构

OpenTelemetry可以接收trace、metric、log数据, 这些数据可以是服务上报,也可从诸如kafka之类的中间件中获取。
服务数据可以使用官方提供的sdk,在代码中进行打桩上报。如果是java、dotnet这样的开发语言,官方也提供了代码注入的途径;go这种不支持注入的(也有编译时插桩的方案),官方提供了EBPF的内核hook。
阿里云编译插桩: https://github.com/alibaba/opentelemetry-go-auto-instrumentation
OpenTelemetry: https://github.com/open-telemetry/opentelemetry-go-instrumentation
Collector可以分为三个角色:
- 接收器。 接收支持多种数据源或者数据格式, 所以了老的系统如zipkin等也可以无缝迁移; 也可以接入消息队列等进行削峰
- 处理器。 接收到的数据可以进行清洗处理。如在trace场景下,正常的链路实际没有太大的价值, 所以可以使用尾部采样策略,只采错误的链路。
- 导出器。 将处理后的数据导入到对应的中间件服务中。 如写入到ck、es等存储中间件, 也可以写入到kafka等消息队列削峰, 再由其他的导出器订阅数据并写入存储。
- 连接器。 连接器其实是一个导出器和接收器的整合(所以我没有将它算到角色中),用于在collector内部进行数据处理和流转。
监控指标
在我们的平台中,可观测项目产生的指标大概可以分为两个来源:
- 服务接入网格后, 由sidecar、gateway暴露的istio指标
- 服务接入OpenTelemetry后,由sdk生产的源站指标和程序堆栈指标
网格指标采集
网格中的指标采集涉及到pod的发现,而pod发现又涉及到k8s权限问题(跨部门的权限沟通真的很头痛),所以是沿用已经存在的监控系统: serviceMonitor+prometheus
进行采集,然后remotewrite
到vm集群中。

网格的指标我们主要用两个istio_requests_total
和istio_request_duration_milliseconds_*
。 因为涉及到流量方向、请求path、状态码等信息存在label中,所以数据量比较大, 每天大概有2亿左右Series。
有尝试过对网格指标的label在prometheus推送时进行删减, 结果是数据乱掉了(因为删掉了一些标识性的label),精简label这个事情就暂时搁置了。
服务指标采集
服务接入OpenTelemetry后,由sdk生产的源站指标和程序堆栈指标采集方式有两种:
serviceMonitor+prometheus
进行采集,然后remotewrite
到vm集群中。 这个逻辑和网格指标是一样的。由prom采集的好处是可以将一部分压力分摊到业务的pod上, 坏处就是prom的单点采集瓶颈会比较大,我们也在探索使用vmagent替代prom的方案。- 远程写入到OpenTelemetry Collector, 由collector写入到vm集群中。
OpenTelemetry Collector
目前对于metric的processor
支持不是太好,实际使用下来性能和功能上都差强人意,好在我们目前没有太多对指标进行处理的需求,这一块后期还是有很大的探索和优化空间的
指标存储方案
指标存储对比过thanos
和vm
,最终选择了vm
进行存储, prometheus
更多的是采集的角色。
存在多套集群、多套prometheus
的环境下, thanos
的查询效率实在太低了。举个例子来说,对于实时查询来说,thanos查询时对于每个后端的prom节点发送的都是全量查询数据, 如果涉及到历史数据,还需要加载bucket,不仅查询效率慢,而且对后端prom的压力很大。
相对来说vm
本身实现了时序数据的存储机制, 集群模式更是可以将采集、查询、插入等模块分开, 不论从性能和架构上都更加清晰,也更容易横向扩展。
我们目前是6台48c512g的物理机裸跑, 承载每天10亿的Series。 这个vm集群即支持了监控报警,又支持了内部观测平台上的查询功能。
全链路trace数据
trace数据是整个可观测体系的核心, 也是最大的数据来源。
我们每天大概有1200亿 的span数据被发送到collector中, 最终根据各种采样策略后, 会有250亿 的span数据存在数据库中, 并且保留7天。 这个数据量还在随着用户接入逐步提升。
我们的trace处理体系是脱胎于signoz的,后面根据内部的业务情况和数据使用又诞生了自己的collector处理体系。
trace来源
从上报方式来分有两种:
- 程序直接上报trace/span到collector
- 从日志中解析trace,清洗后转换为trace/span信息,上报到collector

对于trace处理的优化主要集中在以下几点:
- 如何提高向
clickhouse
写入的效率 - 要对trace进行尾部采样处理,需要预先对同一个trace链路的span进行整合, 如何高效进行
- 日志数据因为存在延迟, 所以会导致trace链路不完整
我们在对这几点进行对应的处理后, ck的吞吐量提升30%以上, 处理所占的资源也有20%左右的下降。 后面的文章会分享这一部分的优化。