OpenTelemetry的工作原理:一份全面的指南
如果你是一名软件开发人员或DevOps工程师,那么你很可能已经听说过OpenTelemetry。在讨论可观测性、监控或分布式系统的调试时,这个术语经常会被提及。 你可能也知道它的基本定义,但了解OpenTelemetry是什么与真正理解它的运作原理其实是两回事。 读完本指南后,你将能够明白OpenTelemetry是如何从端到端工作的——从请求进入你的应用程序的那一刻起,直到这些数据被显示在可观测性后端系统中。你还会了解到追踪信息、时间跨度、上下文传播机制以及数据导出工具是如何共同构成一个完整的处理流程的。 如果你完全不了解OpenTelemetry,也别担心:接下来的部分会帮助你快速掌握相关
如果你是一名软件开发人员或DevOps工程师,那么你很可能已经听说过OpenTelemetry。在讨论可观测性、监控或分布式系统的调试时,这个术语经常会被提及。
你可能也知道它的基本定义,但了解OpenTelemetry是什么与真正理解它的运作原理其实是两回事。
读完本指南后,你将能够明白OpenTelemetry是如何从端到端工作的——从请求进入你的应用程序的那一刻起,直到这些数据被显示在可观测性后端系统中。你还会了解到追踪信息、时间跨度、上下文传播机制以及数据导出工具是如何共同构成一个完整的处理流程的。
如果你完全不了解OpenTelemetry,也别担心:接下来的部分会帮助你快速掌握相关知识,以便我们能够继续深入学习。
目录
什么是OpenTelemetry?
OpenTelemetry是一个开源的、与特定供应商无关的可观测性框架。它为你提供了一种标准化的方法,用于为你的应用程序添加监控代码、生成监控数据,并将这些数据导出到你选择的任何可观测性后端系统中。
在OpenTelemetry出现之前,每种监控工具都有自己独特的数据收集方式。如果你使用Datadog,就必须按照Datadog的要求为你的应用程序添加监控代码;而如果换成Jaeger,又需要重新开始这一过程。OpenTelemetry的出现改变了这一状况,因为它为你提供了一种统一的标准方法,无论你使用的是哪种后端系统,都可以用同样的方式来为应用程序添加监控功能。
[!注意] OpenTelemetry并不是一个监控平台、仪表盘或数据存储工具。它提供了用于从应用程序中收集遥测数据并将其传输到观测后端的工具和标准,这些数据可以在后端被存储、查询和分析。
OpenTelemetry收集的数据被称为遥测数据。这些数据是您的应用程序在运行过程中产生的关于自身状态的信息,它们分为三种形式:
追踪记录可以显示请求在您的系统中是如何传输的。
指标数据会提供一些数值信息,比如您的应用程序每秒处理多少个请求,或者它使用了多少内存。
日志记录
则是对应用程序内部发生的特定事件进行时间戳标记后的记录。
OpenTelemetry的工作原理
当有请求到达您的应用程序时,系统内部会发生许多操作。OpenTelemetry的任务就是捕获所有这些操作过程(包括追踪记录、指标数据和日志记录),并将它们发送到正确的位置。
其工作流程如下所示:
每个阶段都有特定的功能。让我们逐一了解这些阶段。
步骤1:为您的应用程序添加监控代码
在OpenTelemetry能够开始收集数据之前,首先需要为您的应用程序添加监控代码。所谓“添加监控代码”,其实就是编写一些代码,告诉OpenTelemetry应该关注哪些信息以及需要记录哪些内容。
有两种方式为应用程序添加监控代码:自动方式或手动方式。
1. 自动添加监控代码
这是最简单的方法。您只需将相应的库添加到项目中,它就会自动为您的应用程序添加监控功能,而无需修改现有的代码。
例如,如果您正在运行一个Node.js Express应用程序,您可以添加OpenTelemetry的自动监控包,这样它就会自动开始收集HTTP请求、数据库查询等信息。
具体的设置方法如下:
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
instrumentations: [[nodeAutoInstrumentations()],
});
sdk.start();
在您的应用程序启动之前运行这段代码,OpenTelemetry就会开始自动收集遥测数据。有关完整的设置指南,请参阅在Node.js中使用OpenTelemetry入门。
2. 手动代码监控
自动代码监控功能非常强大,但它无法捕捉到你自己编写代码中发生的所有情况。如果你想追踪某个特定函数内部的执行过程,比如处理一笔支付或验证用户信息需要花费多少时间,你就需要自行添加相应的监控代码。
下面来看一个简单的例子。假设你有一个用于处理订单的函数:
function processOrder(orderId) {
// 处理逻辑
}
通过手动代码监控,你可以这样编写这个函数:
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('order-service');
function processOrder(orderId) {
return tracer.startActiveSpan('processOrder', (span) => {
// 处理逻辑
span.end();
});
}
这样做的好处是,你会创建一个“跟踪记录”。这个记录会记录下`processOrder`函数何时开始执行、何时结束,以及整个执行过程花费了多长时间。关于“跟踪记录”的更多信息,你可以在第三步中了解到。
如需查看完整的手动代码监控参考资料,请访问OpenTelemetry JavaScript代码监控指南。
步骤2:OpenTelemetry生成遥测数据
一旦你的应用程序完成了代码监控配置,OpenTelemetry就会开始生成关于该应用程序运行情况的遥测数据。这些数据以三种形式存在,我们之前已经简单介绍过:跟踪记录、指标数据以及日志记录。每种类型的数据都能帮助我们解答不同类型的监控问题。
跟踪记录可以显示一个请求在系统中是如何流转的,它经过了哪些服务,以及每个步骤所花费的时间。指标数据则会提供随时间变化的数值信息,比如请求处理速度、错误率或内存使用情况。日志记录则是对应用程序中发生的特定事件的详细记录,其中会包含时间戳信息。
下面是一个简单的对比表格:
| 类型 | 展示的内容 | 示例 | 解决的问题 |
|---|---|---|---|
| 跟踪记录 | 请求在系统中的流转路径 | 一个订单结算请求依次经过API、订单处理服务以及数据库 | 为什么这个请求会变慢?哪里出现了问题? |
| 指标数据 | 随时间变化的数值 | 每秒200个请求,平均响应时间为95毫秒 | 我的应用程序目前运行正常吗? |
| 日志记录 | 特定事件的详细信息 | ERROR: 订单#1234的支付操作失败 |
在这一点上究竟发生了什么? |
步骤3:追踪请求在应用程序中的传播过程
当用户向您的应用程序发送请求时,该请求通常会经过多个服务才能得到响应。而“追踪记录”就是完整记录这一整个过程的数据,从请求进入系统开始,直到请求完成为止。
实际上,一条追踪记录是由许多被称为跨度的较小单元组成的。每个跨度代表一项操作,比如一次API调用、一个数据库查询或一个函数的执行,这些跨度共同构成了事件发生的完整过程。
每条追踪记录都有一个唯一的编号,而每个跨度也会有自己的编号。正是这个编号将所有的跨度连接在一起。无论请求经过了多少个服务,它们都共享同一个追踪编号,因此您可以在观测后端系统中从头到尾追踪这条请求的整个流程。
下面举一个简单的例子:用户下了一单订单,此时请求会依次经过四个服务:
每个跨度都有开始时间和结束时间,因此您可以清楚地了解每项操作所花费的时间。如果某些环节出现延迟或失败,只需查看这些跨度信息,就能准确判断问题出在哪个步骤。
步骤4:上下文传播使服务间的协作成为可能
在步骤3中,我们了解到一条追踪记录是由多个服务的跨度组成的。但这里有一个问题需要思考:OpenTelemetry是如何知道支付服务中的某个跨度与订单服务中的某个跨度属于同一条追踪记录的呢?
如果没有某种机制将它们连接起来,每个服务都会独立地记录自己的操作过程。API网关会记录一项操作,订单服务会记录另一项操作,而支付服务又会记录第三项操作。这些操作看起来就像是完全独立的请求,彼此之间没有任何关联,这样一来,跨服务进行调试就会变得几乎不可能。
这时,上下文传播机制就发挥了作用。当请求从一个服务传递到另一个服务时,OpenTelemetry会将其追踪上下文附加到请求中,通常是通过HTTP头部来实现这一点的。这个上下文包含了追踪编号和父跨度编号,因此每一个处理该请求的服务都能知道它属于哪条追踪记录,以及它在整个流程中的位置。
下面是实际操作中的示意图:

这三项服务都使用相同的追踪ID。正是这一机制使得你的观测后端能够将这些数据片段连接起来,形成一个完整的追踪记录。
OpenTelemetry并没有为自己制定专门的规则来处理这个问题。它遵循W3C追踪上下文标准,这一被广泛采用的规范定义了追踪上下文应该如何格式化以及在不同服务之间如何传递,因此OpenTelemetry能够在各种语言、框架和供应商的环境中保持一致性。
好消息是,如果你使用了自动数据采集功能,那么上下文的传递过程会自动完成。OpenTelemetry会帮你处理所有相关细节,因此除非你正在使用自定义的传输机制或非标准的配置方案,否则根本不需要担心这个问题。
步骤5:OpenTelemetry SDK负责处理这些遥测数据
在当前阶段,OpenTelemetry正在收集遥测数据,并确保这些数据能够在不同的服务之间被连续追踪。但是,从数据片段被创建出来的那一刻起,直到它们离开你的应用程序,还必须有某个组件来对这些数据进行处理——而这正是SDK的任务。
当你使用自动数据采集功能编写代码时,这些数据是通过OpenTelemetry API被创建的。作为开发者,你可以通过这些API来进行相关操作,比如调用trace.getTracer()或tracer.startActiveSpan()等方法。不过,API本身并不会处理或发送任何数据,它需要背后的SDK来实际完成这些工作。
一旦SDK接收到了遥测数据,它就会将这些数据传递给处理器。处理器的职责包括将多个数据片段批量处理后再发送出去、添加额外的属性,或者过滤掉不需要的数据。其中最常用的处理器是BatchSpanProcessor,它能够将多个数据片段分组后批量发送,从而在生产环境中提高效率。
甚至在处理器开始工作之前,SDK还会先进行数据采样。通过采样功能,你可以控制实际需要收集多少遥测数据。在流量较大的应用程序中,如果记录所有的数据片段,将会产生海量的数据。而通过采样,你可以指定SDK只捕获一定比例的数据,这样既能保证数据的完整性,又能有效控制成本和数据量。
当处理器完成处理工作后,它会将处理后的数据传递给导出器,而导出器才会真正将这些数据发送到目的地。在下一步中,你会了解到这一过程的具体实现方式。
步骤6:导出器将遥测数据发送出去
出口商的工作很简单:只需获取SDK提供的遥测数据,然后将其发送到您所配置的目标地址即可。OpenTelemetry使用OTLP(即OpenTelemetry协议)来传输遥测数据。这是一种专为传输跟踪信息、指标数据及日志而设计的标准协议,它可以通过HTTP或gRPC进行数据传输。
[!注意] OTLP与OpenTelemetry并非同一概念。OpenTelemetry是一个完整的框架,涵盖了数据采集工具、SDK、收集器等功能;而OTLP仅仅是该框架中用于数据传输的协议而已。
虽然OTLP是默认使用的协议,但并非所有数据导出工具都会采用它。有些导出工具会以自己特定的格式将数据直接发送到后端系统,比如Jaeger或Prometheus。因此,根据您的配置需求,您可以选择使用OTLP导出工具将数据发送给收集器或后端系统,或者选择使用特定于某个供应商的导出工具来直接传输数据。
步骤7:OpenTelemetry收集器接收并处理数据
OpenTelemetry收集器是一个独立的服务组件,它位于您的应用程序与后端观测系统之间。该收集器会接收遥测数据,对其进行处理,然后将其转发到一个或多个目的地。
使用收集器是一种常见的做法,但并非强制要求。您也可以配置数据导出工具,使其直接将数据发送到后端系统,从而跳过收集器的环节。但在大多数生产环境中,开发团队还是会选择使用收集器,因为这样他们就可以在一个中心化的位置来管理所有的遥测数据,而无需修改应用程序的代码。
收集器的处理流程分为三个阶段:
接收器会从您的应用程序中接收遥测数据,这些数据通常是通过OTLP协议传输过来的。
处理器会在收集器这一层对数据进行加工处理,比如将多个跟踪事件合并成批次、过滤掉无关信息,或者添加额外的属性,之后再继续进行数据传输。
导出器会将处理完毕的数据发送到后端系统。如果需要的话,也可以将数据发送到多个后端系统。
以下是一个最基本的收集器配置示例:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
exporters:
otlphttp:
endpoint: https://your-backend.com
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
这个配置示例中,数据会通过gRPC协议被接收进来,然后被合并成批次以提升传输效率,最后通过HTTP协议发送到后端观测系统。
收集器还可以同时接收来自多个应用程序的数据,并将这些数据转发到一个或多个目的地。因此,各个应用程序并不需要直接将遥测数据发送到后端系统,而是先将数据发送给收集器,由收集器来负责后续的处理工作。
有关完整的配置信息,请参阅OpenTelemetry收集器配置文档。
步骤8:后端观测系统存储并分析遥测数据
一旦遥测数据离开了收集器,它就会到达后端观测系统,在那里被存储起来并进行进一步分析处理。
后端负责存储你的数据,允许你对其进行查询,并提供仪表盘以及警报功能,这些都是你日常会使用的功能。而OpenTelemetry本身并不提供这些功能——它只是将数据传输到后端,后续的处理工作则由后端来完成。
一些原生支持OpenTelemetry的常用后端包括:
开源工具: Jaeger、Prometheus、Grafana Tempo
商业产品: Datadog、New Relic、Honeycomb、Dynatrace、Elastic、Lightstep、Grafana Cloud等。
一旦数据被传输到后端,你就可以通过追踪日志来排查缓慢响应的请求问题,构建仪表盘来监控应用程序的运行状态,并在出现异常时设置警报机制。
整合OpenTelemetry的数据流
假设用户通过手机银行应用发起了一笔转账请求。该请求会首先到达你的API网关,由于你的应用程序已经安装了OpenTelemetry的监控模块,系统会立即开始收集相关数据。系统会为这个请求创建一个“跨度”对象,并为其分配一个唯一的追踪ID。
当请求被转发到认证服务时,该追踪ID会通过请求头被携带过去;认证服务也会为自己创建一个新的跨度对象,并将其与原来的追踪关联起来。同样的过程也会在认证服务调用交易服务、以及交易服务访问数据库处理转账请求时发生。最终,这四个服务中的四个跨度对象都会通过同一个追踪ID连接在一起。
与此同时,SDK会在后台处理这些监控数据,将这些跨度对象传递给批量处理工具,然后再由导出器将它们打包成OTLP格式,发送给收集器。收集器会根据预设的规则对数据进行处理,最后将其传输到你的后端系统。
下面是这个数据流中涉及的所有组件的简要说明:
| 组件 | 功能 |
|---|---|
| 监控模块 | 用于收集应用程序内部发生的所有操作数据 |
| API接口 | 用于暴露代码中调用的方法,从而生成跨度对象、指标数据和日志记录 |
| SDK工具包 | 负责处理监控数据并将其准备好进行导出 |
| 导出器 | 将处理后的数据打包成OTLP格式并发送出去 |
| 收集器 | 接收、处理这些数据,并将其转发到后端系统 |
| 后端观测平台 | 用于存储、查询和可视化这些监控数据 |
通过这个转账请求,你的后端系统中就会形成一条完整的追踪记录。当你打开仪表盘并输入相应的追踪ID进行查询时,你会看到与该交易相关的所有服务、所有的跨度对象,以及每一毫秒发生的详细操作。
你是否需要OpenTelemetry的所有组件?
说实话,刚开始使用OpenTelemetry时,并不需要用到所有的组件。虽然本文介绍的这套流程代表了完整的配置方案,但并不是每个团队都会使用所有这些功能。
不使用收集器时:您的导出工具会直接将遥测数据发送到后端,中间不会经过任何中间环节。这种配置适用于小型项目,或者刚开始使用该技术的时候。
使用收集器时:这是更为常见的生产环境配置方式。当团队需要更多控制能力时,就会添加收集器——比如将遥测数据路由到多个后端、过滤敏感信息,或者集中管理来自数十个服务的遥测数据。
收集器功能强大,但并非强制使用。如果您的系统架构较为简单,可以先不使用它;等到真正需要时再添加即可。
为什么使用OpenTelemetry?
现在您已经了解了OpenTelemetry的各个组成部分是如何协同工作的。以下是使用它的原因:
与供应商无关的集成方案:在OpenTelemetry出现之前,如果更换可观测性工具,就需要从头开始重新为整个应用程序添加监控代码。而使用OpenTelemetry,只需进行一次配置,之后就可以自由切换后端。
跨服务、跨语言的一致性:您的后端可能使用Go语言编写,微服务使用Python,数据管道则使用Java。OpenTelemetry为这些不同的技术提供了相应的SDK,而且它们生成的遥测数据格式都相同,因此整个技术栈能够使用统一的标准进行通信。
关联性的可观测性数据:追踪信息、指标数据以及日志都会被纳入同一个处理流程。因此当出现问题时,指标数据的异常变化可以帮助您找到问题的根源,而日志记录则能指出具体是哪个环节出了故障。
它正在成为行业标准:目前大多数可观测性后端都原生支持OpenTelemetry。因此,每次使用新的工具时,都不需要重新学习特定的集成方法;只需学习一次OpenTelemetry,就可以在各种环境中使用它。
结论
OpenTelemetry为您提供了一种标准化的方法,用于为应用程序添加监控代码、收集遥测数据,并将其发送到您选择的任何后端。这种方案不会让您受到特定供应商或工具的限制。
如果这篇文章中只有这一点对您有帮助,那么请记住:您需要为应用程序添加监控代码,生成遥测数据,处理这些数据,然后将其导出并进行分析。每一个步骤都有相应的功能模块,现在您已经知道了它们各自的作用。
如果您刚开始使用这项技术,不必一次性设置所有内容。可以先从添加监控代码开始,让遥测数据开始传输到后端,等到需要时再添加收集器。随着需求的变化,您随时都可以进一步扩展配置。
如果这篇文章对您有帮助,我很乐意与您继续交流。您可以在LinkedIn或X上找到我。如果您有任何疑问,或者只是想讨论可观测性技术及开发工具相关的话题,随时欢迎与我联系。
相关文章
如何使用Hono和Zod构建类型安全的API
如果你之前曾经开发过 Node.js API,那么你就应该了解这种麻烦:TypeScript 中定义的类型与运行时验证的结果不一致,而 OpenAPI 文档的内容也与这两者都不同。 有时有人会在接口中添加新的字段,但相应的模式文件却从未得到更新。因此,文档内容会一直保持过时的状态,直到有客户端提交错误报告为止。这种情况下,既不会出现编译错误,也不会有测试失败的情况——这三个信息来源就这样出现了分歧。 在本教程中,你将学习如何使用 Hono 和 Zod 将这些问题统一起来。你会学到我在实际开发中使用的那些模式,包括我在维护的开源视频处理工具包 ClipForge 中所采用的方案。这些方法能够确保
阅读全文
在React中处理高频实时数据:从环形缓冲区到离屏canvas技术
React在很多方面都表现得非常出色。但如果你曾经尝试过每秒向它传输数千个数据点,你就会很快意识到:React并不像一根能输送大量水流的消防水管,而更像是一根普通的花园浇水软管。 如果强迫它处理过多的数据,要么会导致“草坪被淹没”(即DOM结构变得混乱),要么会使得“管道爆裂”(也就是应用程序运行出现严重问题)。 还有另一个与上述观点相关的观察结果:你的笔记本电脑通常拥有8到16个CPU核心,而你的React应用程序几乎总是只使用其中的一个核心。主线程负责处理JavaScript代码、DOM操作、布局计算以及绘制工作;而其他核心则处于闲置状态,因为主线程实在难以维持每秒60帧的渲染速度。 这两
阅读全文
为什么你绝不应该在API请求中包含电子邮件地址
在开发环境中,你的注册接口看起来没有任何问题。用户完成注册后,你会将相关数据保存到数据库中,然后调用邮件服务提供商,并返回状态码 201 Created ,这样用户就会收到欢迎邮件。一切似乎都很顺利。 然而,当生产环境中的请求开始涌入时,问题出现了: 邮件发送接口现在需要2秒钟才能完成响应,而不是原本的200毫秒。因此,有些请求会超时失败。而在邮件服务提供商出现故障的情况下,所有注册请求都会返回状态码 500 。技术支持人员很困惑:为什么用户能够创建账户,但却始终收不到确认邮件链接? 其实问题并不出在你的邮件模板上,而在于你选择将邮件发送处理逻辑放在HTTP请求路径中这一决策。 在这篇文章中,
阅读全文
布隆过滤器解析:这种概率数据结构是如何为Instagram、谷歌以及各种大规模系统提供支持的
Instagram拥有超过5亿个注册用户名。当有新用户尝试注册时,平台几乎需要立即回答一个问题:这个用户名是否已经被别人使用了? 一种简单的解决方法是进行数据库查询——从用户表中查找该用户名,判断它是否存在。这种方法在数据量较小时效果不错,但当数据库记录数量达到5亿条,且每天要被查询数百万次时,就会成为一个严重的性能瓶颈。即使为数据库添加了索引,每次注册尝试时,数据库仍然需要执行复杂的操作。 幸运的是,还有更聪明的解决方法。在触碰数据库之前,可以先询问另一个系统来快速获取答案。这个系统会给出两种可能的回答: 肯定没有被占用: 这个结论是绝对可靠的,没有任何例外情况。这意味着该用户名是可以使用的
阅读全文