先从一个工作场景开始。早上到工位还没坐稳,同事就在群里喊:线上那个接口的响应时间又上来了,到底是哪台机器慢了?我打开查询面板,输入环境、服务名、时间段,把接口耗时、调用链、错误日志叠在一起看了两分钟,定位到是下游数据库连接池满了,顺手把慢查询捞出来扔给DBA。整个过程里,我看的不只是日志,而是指标、链路、事件混在一起的一整套数据。把这些数据从应用、容器、数据库里自动采集出来,统一传输、存储、分析、展示的这一整套能力,就叫 Telemetry,也就是遥测。
这文章不是教科书式的名词解释,我想从一个干过几年可观测性相关工作的工程师视角,把 Telemetry 到底是什么、内部有哪些环节、实际怎么搭一套、会遇到什么坑,完完整整说清楚。适合刚接触可观测性的开发,也适合做运维、SRE、嵌入式设备相关工作的朋友。看完之后,至少你能自己动手搭一条最简遥测管道,也能在别人的系统里看懂数据是怎么流起来的。
1. 到底什么是 Telemetry:先别急着喷概念
1.1 从字面和历史一起看这个词
Telemetry 这个词由两个希腊词根组成:tele 是“远程”,metron 是“测量”。合起来就是远程测量——把远端的物理量或状态量,通过某种方式传到接收端做分析。这个概念比计算机早得多。最早的实用场景在航天领域,运载火箭飞行过程中,地面站需要实时知道发动机温度、舱压、燃料余量,不可能有人跟着火箭上去抄表,于是传感器采集数据,再通过无线电链路把数据发回地面,这就是经典的遥测系统。
放到软件系统里,遥测的含义并没有变。只是把“无线电链路”换成了网络传输,把“传感器”换成了埋点探针,把“地面站”换成了指标存储和可视化平台。你服务里每秒处理的请求数、队列积压量、磁盘 IO、GC 停顿时间,这些数据被采集、上报、聚合,最终呈现在监控大屏上。这一整条链路,都是遥测。
很多人问,那这和监控有什么区别?我的理解是,监控是消费侧的行为,它依赖遥测提供的上游数据。遥测强调的是从采集到传输再到分析展示的完整闭环,监控只是其中“展示与分析”的一个子集。如果你只搭了监控大屏,但数据是人工手动上报、或者三天两头就断了,那严格来说你没有一套合格的遥测系统,只有几张画得不错的图表。
1.2 现代遥测管道的四个环节
把遥测拆开看,无论多复杂的系统,都逃不出四个环节:采集、传输、存储、消费。
采集环节解决的是“数据从哪来、怎么取”的问题。轻量做法是在业务代码里埋点,或者用类似字节码注入的自动探针方案;重一点的做法,在基础设施层面用采集器或者内核模块直接抓取指标。这一环决定了数据质量,埋点埋漏了、埋错了,后面所有环节做得再好也白搭。
传输环节解决的是“数据怎么送出去”的问题。要考虑序列化协议、压缩方式、传输可靠性、背压策略,甚至是网络断连时的本地缓存取舍。很多初学者的误区是以为只要把数据发出去就行,但其实传输层最容易出幺蛾子,后文我会专门讲坑。
存储环节负责把遥测数据落盘,通常是时序数据库或者链路存储,再配合对象存储做冷热分层。这一环决定你能回溯多久的数据,以及查询能跑多快。
消费环节是用户能感知到的地方:面板、告警、排障、容量规划,全在这层。大部分人对遥测系统的直观印象来自这一层,但真正决定体验上限的其实是前三个环节。
2. 遥测系统的核心部件拆解
2.1 四大数据形态:指标、日志、链路、事件
遥测系统处理的数据,目前业内公认拆成三大件,加上一个不算核心但越来越常见的事件型数据,一共四类。
第一类是指标,也就是 Metrics。它本质上是一系列带时间戳和标签的数值序列,比如 QPS、P99 延迟、CPU 使用率。指标的特点是结构固定、体积小、适合聚合和告警,但不适合看单个请求的细节。你能从指标知道“系统出问题了”,但很难从指标知道“到底是哪个账号的哪次请求触发了问题”。
第二类是日志,也就是 Logs。日志是离散的、带时间戳的文本记录,包含上下文信息。日志的优点是极其详细,缺点是量级巨大,非结构化程度高,检索成本高。生产环境里一天几十 GB 日志很常见,排查问题时它往往是最终答案所在。
第三类是链路追踪,也就是 Traces。一条 Trace 代表一个请求从入口到出口经过的所有服务的完整路径,每跳是一个 Span,记录服务名、操作名、耗时、状态。链路数据把前两者的缺口补上了,它能清晰地告诉你“慢在哪一跳”,但它的采样策略很考验设计水平,全量采样会让存储成本直接爆掉。
第四类是事件,比如告警事件、部署事件、配置变更。这类数据更像一种额外的时间线注解,在排障时经常起到“案发时间确认”的作用。比如你发现一个服务的错误率升高,恰好同时有一条配置变更事件,那嫌疑范围一下子就能缩小。
这四类数据不是说必须都上才算遥测,但成熟系统通常至少覆盖指标、日志、链路三种。缺了日志,指标异常时很难深挖;缺了链路,多服务调用的瓶颈极难定位;缺了指标,连“什么时候开始变慢”都不知道。
2.2 数据建模与语义约定:遥测系统的“通用语”
搞遥测不能各说各话。你的指标里有一个叫 qps 的字段,我的系统里叫 request_rate,两套系统之间的数据完全无法互认。所以现代遥测框架里引入了语义约定,也就是 Semantic Conventions。
语义约定可以理解为一套行业统一的命名规范,比如 HTTP 请求的指标应该用 http.server.request.duration 来表示,属性名应该用 http.request.method、server.address 这类标准键来记录。这样大家采集出来的数据长得差不多,标准面板、标准告警规则就能跨系统复用,也方便在多工具之间切换。
除此之外,还有一个容易被人忽略的概念:资源属性,也就是 Resource Attributes。它描述的是“这份数据从哪来”,包括服务名、服务实例 ID、部署环境、宿主主机信息。看起来不起眼,但排障时非常关键。同样是错误率指标,你至少得能区分是生产环境还是预发环境、是哪个实例上报的。如果没有资源属性,数据到了存储层就像一堆没有寄件人地址的信件,只能按时间戳堆在一起,几乎没法做维度过滤。
我在接触 OpenTelemetry 之前,自己做过的埋点就是裸奔的:只上报了指标名和值,没有携带资源信息。当时不觉得有问题,后来服务从几个实例扩容到了几十个实例,看曲线发现聚合后的数值忽高忽低,但完全不知道是不是某个实例拖了后腿。后来把所有指标补上了 service.name、service.instance.id 这些资源维度,才真正拥有了下钻能力。这是遥测系统从“能跑”到“好用”的重要分水岭。
2.3 传输协议与数据可靠性
采集到数据之后,要通过网络把数据送到后端。这里有两个层面的选择:传输协议和序列化格式。
传输协议负责把数据从一个端点搬到另一个端点。常见选项包括 gRPC 流式传输、普通 HTTP 请求、以及轻量级场景下的 UDP 上报。gRPC 是目前主流遥测框架的默认选择,它支持双向流式传输、二进制协议、压缩率高,还能通过流控机制避免对后端造成压力。HTTP JSON 上报胜在简单,调试方便,但序列化效率低,通常只用于轻量采集器或者 API 网关的旁路场景。
序列化格式层面,常见的有 Protobuf、JSON、MessagePack 等。生产环境我会优先选 Protobuf,原因很直接:解析性能高、传输体积小、Schema 明确,基本是行业事实标准。JSON 适合开发环境和调试,但直接在线上大规模传输,会让带宽和 CPU 成本双双走高。
传输可靠性是很多人容易忽略的设计点。网络不可能永远不抖动,如果你的遥测客户端直接同步阻塞上报,请求一慢就会拖垮业务线程;如果用异步但无限缓存,数据量大了又会把内存堆爆。成熟的方案通常用有界队列加背压策略:队列满了,要么丢弃新数据,要么采集失败样本,但绝不能让业务代码跟着一起崩。丢弃的数据,需要有统计计数,至少让你知道丢了多少,而不是黑盒一样全部蒸发。
3. 从零搭建一条 Telemetry 管道:实操全记录
3.1 环境规划与组件选型
你不需要一上来就上整套商业可观测平台,自己搭一套最小可用的遥测管道,其实几百行配置就能落地。我这次以指标和链路为主线,搭这么一条链路:业务应用侧通过 OpenTelemetry SDK 埋点,然后上报给同一个网络里的采集器,由采集器做一次轻量处理,再分别导出到时序库和链路存储里,最后由可视化面板统一查询。
组件选型上,我推荐尽量用通用标准件,而不是因为图新鲜选小众项目。采集端用 OpenTelemetry Collector;指标存储用 Prometheus 或者兼容 Prometheus 协议的时序库;链路存储用 Grafana Tempo 或者同类产品;展示端用 Grafana。这组搭配的好处是每个环节都有大量现成案例,排障资料丰富,社区踩坑记录也多,不至于遇到问题只能自己去翻源码。
实际环境里我建议先在一台性能还行的 Linux 服务器上跑所有组件,没必要一上来就搞 Kubernetes 集群。遥测管道本身的调试复杂度已经不低,加上调度系统的变量,新手很容易分不清是组件配置问题还是容器网络问题。先在裸机上跑通,再考虑容器化迁移,性价比更高。
3.2 业务侧埋点:以 Python 服务为例
先做一个最简单的埋点示例。假设你有一个 Python 写的 Web 服务,需要上报每个请求的延迟和自定义业务指标。以下是常见做法:
from opentelemetry import metrics, trace from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.trace import TracerProvider from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource, SERVICE_NAME resource = Resource.create(attributes={ SERVICE_NAME: "order-service" }) # 配置 Trace trace_exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True) trace_provider = TracerProvider(resource=resource) trace_provider.add_span_processor(BatchSpanProcessor(trace_exporter)) trace.set_tracer_provider(trace_provider) # 配置 Metrics metric_exporter = OTLPMetricExporter(endpoint="http://localhost:4317", insecure=True) meter_provider = MeterProvider(metric_readers=[PeriodicExportingMetricReader(metric_exporter, interval=10000)], resource=resource) metrics.set_meter_provider(meter_provider) # 业务代码里创建计时工具,记录下单耗时 tracer = trace.get_tracer(__name__) meter = metrics.get_meter(__name__) order_latency = meter.create_histogram("order.latency", unit="ms", description="下单处理耗时") def create_order(req): with tracer.start_as_current_span("create_order") as span: start = time.time() # 这里是业务逻辑 result = do_create_order(req) order_latency.record((time.time() - start) * 1000, {"order_type": req.type}) span.set_attribute("order.type", req.type) return result这段代码做了三件重要的事:一是用 Resource 把服务名固定下来,保证上报到后端的数据有明确的来源标识;二是启动一个 Span,把创建 order 的完整过程纳入链路;三是在一个 Span 内同时记录自定义的直方图指标,这样延迟曲线和调用链可以建立关联。
有个细节值得单独说:PeriodicExportingMetricReader 的 interval 参数我设成了 10000 毫秒,也就是 10 秒一批。这个值不一定要追求越小越好。设得太小(比如 1 秒),采集器要反复建立连接、序列化、传输,CPU 开销会明显上升;设得太大,数据延迟又会变高,出了故障要等很久才能看到曲线变化。一般建议在 5 到 15 秒之间调整,具体看业务对实时性的要求。
3.3 配置采集器做中转与清洗
业务代码不建议直接把遥测数据发到最终存储,中间最好加一层采集器,也就是 OpenTelemetry Collector。它扮演的是遥测数据的交通枢纽角色,可以做过滤、脱敏、聚合、负载均衡,也可以把数据同时发给多个后端。
以下是精简后的 collector 配置示例:
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 processors: memory_limiter: check_interval: 5s limit_percentage: 80 spike_limit_percentage: 25 batch: send_batch_size: 1024 timeout: 5s attributes/name: actions: - key: environment value: production action: upsert exporters: prometheus: endpoint: 0.0.0.0:9091 otlp/tempo: endpoint: tempo:4317 tls: insecure: true service: pipelines: metrics: receivers: [otlp] processors: [memory_limiter, batch, attributes/name] exporters: [prometheus] traces: receivers: [otlp] processors: [memory_limiter, batch, attributes/name] exporters: [otlp/tempo]这段配置里有几个我不能不提醒你的点。
memory_limiter 处理器是用来踩刹车用的,防止采集器本身因为接收数据太多而内存溢出。这里的 limit_percentage 设为 80%,意思是当采集器的内存达到总内存的 80% 时,开始拒绝新遥测数据。别小看这个保护机制,我记得有一次某个服务的埋点逻辑写岔了,一秒钟产生了上千万个 Span,要是没有这个限制,前端采集器会直接挂掉,连带着所有服务的遥测数据全部中断。
batch 处理器负责把零散的小包合并成大批次再导出。这看起来是个小优化,但实际提升非常明显。一次性发送 1024 条数据的吞吐量和逐条发送相比,性能差别不是一点半点。这个处理器的存在,是遥测管道的传输成本能压下来的关键。
attributes/name 这个处理器是我临时加的一个自定义字段注入动作,给所有遥测数据统一打上 environment=production 的标签。这个操作的意义在于,当你有多套环境都往同一个后端上报时,环境标签可以确保你在面板上能彻底切开不同环境的数据。你可以按自己的需求改成其他维度的标签,比如数据中心、版本号。
3.4 指标存储、链路存储与面板联动
指标侧我选择了 Prometheus 兼容模式。需要注意,你的采集器导出端点是 9091,而不是默认的 9090。这是因为 9090 通常是 Prometheus 主服务的端口,而 9091 我留给了采集器作为指标暴露端口,再由 Prometheus 从这个端口去抓取。用拉模型的好处是天然适合时序库的抓取习惯,不用指标数据自己往 Prometheus 里塞,而是等它来取,这样可以避免多实例上报同一个系列时产生写冲突。
链路侧我用的是 OTLP 协议直接导到一个轻量级链路存储。这部分配置相对简单:只需保证链路存储的 4317 端口可以被采集器访问,同时确认它的存储目录有足够的磁盘空间,毕竟链路的数据量比指标大很多,采样率再高,也会撑起不小的存储占用。
面板联动这块,我的习惯是给链路存储建一个数据源,同时把指标数据源也配上,这样在 Grafana 里看到指标曲线出现尖峰时,可以一键跳转到对应的链路检索页,将时间范围自动带过去。这个联动操作在排障过程中属于高频动作,配置好后能显著减少来回切换数据源的时间。
4. 遥测落地最常见的坑与排查实录
4.1 数据不齐、间隔缺失的真相
很多人在看监控面板时发现曲线有锯齿或者断点,第一反应是存储出问题了。其实实践中,这类问题一半以上出在采集端和传输端。
最常见的原因是发送端的队列满了之后直接丢弃数据。OpenTelemetry SDK 默认的队列容量是有上限的,当业务瞬时并发非常高,比如秒杀场景,生成的 Span 数量远远超过导出速度,队列就会溢出。SDK 默认策略是丢弃新数据,保住前端的稳定性,导致面板上看到那条时间范围的曲线明显缺少数据点。
排查的办法很简单:仔细看 SDK 暴露的导出错误统计和队列溢出计数。如果发现 dropped 计数在持续增长,说明系统在高负载下进入了丢弃模式。这时候不要急着加大队列,而是要评估是否需要调高采集端的并发导出能力,或者在前端用一个本地文件系统做临时缓存,等业务低峰期再把数据补传上去。大多数情况下,直接调大队列只会推迟丢数据的时点,而不是解决问题。
还有一类数据缺失是时间戳错位导致的。多个进程各自的时钟如果不一致,上报的数据时间戳就会漂移,到了时序库里会出现乱序。Linux 服务器还好,嵌入式设备和部分容器环境经常有这个问题。处理方案是在每个节点启用时间同步服务,并在采集端配置最大允许时间偏移。如果发现数据点插入时序库时被拒,检查源端时间戳是一个优先方向。
4.2 高基数问题:别让指标维度失控
高基数问题可以说是遥测系统上了规模之后最无情的杀手。通俗解释就是,你的指标维度组合太多,比如每个用户 ID 都作为一个标签值,每个请求 URL 的完整路径都作为一个唯一维度,最终时序库会被撑爆。
我有一个真实案例。早期我们给一个订单服务埋点,用请求完整路径作为标签,比如 /api/v1/orders/12345/detail 这种。刚开始量不大没人在意,后来流量上来,实际生成的时序序列超过了百万条,存储的 CPU 直接满负荷,整个监控系统的查询响应慢到几十秒,连正常的面板打开都卡。
正确的做法是使用聚合后的路径模板,比如把 /api/v1/orders/{id}/detail 作为标签值。同时不要轻易把高基数的标识符作为标签,如果确实需要追踪单个请求,应该用链路 ID 体系来承接,而不是塞进指标维度里。
如果你现在维护的系统指标数量已经很多,建议先统计一下每个指标活跃的序列数量,设置一个阈值,比如超过 10 万条序列的指标就需要重点审查。时序库通常都提供了查询系列数量的接口,定期跑一遍,制成表格看增长趋势,是控制高基数问题的基本功。
4.3 采样策略:链路成本与完整性的平衡
链路追踪的数据量比指标大几个数量级,所以采样几乎不可避免。但采样也不能拍脑袋定一个百分比就算完事,不合理的采样会让链路数据失去排障价值。
最常见的采样方案有三种。第一种是头部采样,也就是在请求刚进来时根据规则决定是否采集整条链路,实现简单,但无法感知请求全貌。第二种是尾部采样,等整个请求完成后统一决定,可以基于最终成功失败状态做精准采样,但要在内存里缓存大量中间状态,成本较高。第三种是优先级采样,对已知的重点业务接口全采,其他接口按比例采样。
我的建议是,如果你还没有能力做尾部采样,优先采用按规则加比例的组合。比如下单、支付这类关键流程,设置 100% 采样,其余只读类接口 10%。这样既能保住核心业务排障能力,又能把整体成本控住。全链路 100% 采样是最省心的方案,适合流量很低的小系统,一旦流量上来,日志和存储成本会很快让你睁大眼睛。
4.4 排查手段与速查表
遥测系统本身也是一套软件系统,它的故障模式并不特殊。排查时我通常按以下顺序推进:先看采集端有没有报错,再看传输端有没有丢包的统计计数,然后看存储端的写入速率和磁盘状态,最后看查询端是不是有问题。
排查遥测链路的问题,最忌讳的是隔着面板猜来猜去。直接去看相关组件自身的状态和监控是最快的路径。比如采集器提供了自身的指标页面,里面包含接收数据速率、导出错误数、队列深度等关键信息。看一眼这些数值,基本就能判断出问题出在上游还是下游。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 时间曲线周期性出现缺口 | 采集端队列溢出丢弃 | 审查导出速率和队列配置,看 dropped 计数 |
| 所有服务数据延迟到达 | 采集端与后端网络带宽不足或握手失败 | 抓包确认 TCP 连接情况,检查是否触发背压 |
| 指标查询极慢 | 高基数序列过多 | 统计系列数,拆掉高基数标签,做聚合 |
| 链路缺少中间某服务节点 | 下游服务没有接入同一套 SDK 或者资源属性不一致 | 检查服务接入面与采集端路由配置 |
| 数据到达后时间乱序 | 节点时钟漂移或采集器批量乱序导出 | 统一 NTP 同步,调整批处理超时与排序逻辑 |
| 改造后曲线翻倍 | 标签变化导致同一指标形成多个系列 | 审查相近指标的去重与命名规范 |
这个表不是万能药,但它能帮我处理掉绝大部分“看起来是监控坏了”的误判。尤其要提醒一点:遥测数据链路处理的是海量小数据包,很多问题不是逻辑错误,而是资源瓶颈和配置不匹配,所以排查时要把动作聚焦在各个组件的状态指标上。
5. 一点留到最后的个人经验
做遥测这几年的一个明显感触是,这个领域最难的从来不是某个组件的配置,而是数据流的全局意识。很多人把遥测理解成“埋点+大屏”,埋完点发现查询不准,就开始折腾可视化公式,但实际的问题是埋点数据本身携带的维度信息就不对。我建议新手在做任何一个埋点之前,都要先在纸上画一遍这个数据从业务代码到最终面板的完整生命线,想想每一跳的格式、协议、标签、容量,这样踩坑率会低很多。
另外分享一个很实用的小技巧:在遥测系统的每个组件上,都要预留健康状态指标,并把它们纳入同一套遥测体系里。也就是说,你的监控系统要监控它自己。采集器的吞吐量、导出的失败率、存储的队列深度,这些数据本身就是遥测数据,必须能被查询到。没有这个自监控能力,一旦链路出问题,你就只能靠猜,那种感觉真的很痛苦。
最后要说的是:遥测方案没有银弹,适合自己业务的才是最好的。初期宁可少上数据,也要保证数据质量;宁可采样率低一点,也不要因为高基数把存储打爆。先把一条链路跑通,再把覆盖面扩大,比一开始就追求万事俱全要稳得多。