第 2 章描述了实现自动分析所需的数据模型,第 4 章描述了支持原生 开源仪表的额外要求,并赋予各角色(应用程序所有者、运维和响应者)自主权。这就是我们对现代可观测性的概念模型。

在本报告的其余部分,我们描述了这个新模型的一个事实实现,即 OpenTelemetry。这一章描述了构成 OpenTelemetry 遥测管道的所有组件。后面的章节将描述稳定性保证、建议的设置以及 OpenTelemetry 现实中的部署策略。有关该项目的更多细节可以在附录中找到。

信号

OpenTelemetry 规范被组织成不同类型的遥测,我们称之为信号(signal)。主要的信号是追踪。日志和度量是其他例子。信号是 OpenTelemetry 中最基本的设计单位。

每一个额外的信号首先是独立开发的,然后与追踪和其他相关信号整合。这种分离允许开发新的、实验性的信号,而不影响已经变得稳定的信号的兼容性保证。

OpenTelemetry 是一个跨领域的关注点(cross-cutting concern),它在事务通过每个库和服务时追踪其执行。为了达到这个目的,所有的信号都建立在低级别的上下文传播系统之上,该系统为信号提供了一个地方来存储它们需要与当前正在执行的代码相关联的任何事务级数据。因为上下文传播系统与追踪系统是完全分开的,其他跨领域的问题也可以利用它。图 5-1 说明了这个分层结构。

image
图 5-1:所有 OpenTelemetry 信号都建立在一个共享的上下文传播系统之上。其他的,非可观测性的交叉关注也可以使用上下文传播机制来通过分布式系统传输他们的数据。

上下文(Context)

上下文对象是一个与执行上下文相关联的键值存储,例如线程或循环程序。如何实现这一点取决于语言,但 OpenTelemetry 在每种语言中都提供一个上下文对象。

信号在上下文对象中存储它们的数据。因为 OpenTelemetry 的 API 调用总是可以访问整个上下文对象,所以信号有可能成为集成的,并在上下文共享数据,而不需要改变 API。例如,如果追踪和度量信号都被启用,记录一个度量可以自动创建一个追踪范例。日志也是如此:如果有的话,日志会自动绑定到当前的追踪。

传播器(Propagator)

为了使分布式追踪发挥作用,追踪上下文必须被参与事务的每个服务所共享。传播器通过序列化和反序列化上下文对象来实现这一点,允许信号在网络工作请求中追踪其事务。

追踪(Tracing)

OpenTelemetry 追踪系统是基于 OpenTracing 和 OpenCensus。这两个系统,以及流行的 Zipkin 和 Jaeger 项目,都是基于谷歌开发的 Dapper 追踪系统。OpenTelemetry 试图与所有这些基于 Dapper 的系统兼容。

OpenTelemetry 追踪包括一个叫做 链接(link) 的概念,它允许单独的追踪被组合成一个更大的图。这被用来连接事务和后台处理,以及观察大型异步系统,如 Kafka 和 AMQP。

指标(Metric)

度量指标(metric)是一个很大的话题,包含各种各样的方法和实现。OpenTelemetry 度量信号被设计成与 Prometheus 和 StatsD 完全兼容。

指标包括追踪样本,自动将指标与产生它们的追踪样本联系起来。手工将指标和追踪联系起来往往是一项繁琐且容易出错的任务,自动执行这项任务将为运维人员节省大量的时间。

日志(Log)

OpenTelemetry 结合了高度结构化的日志 API 和高速日志处理系统。现有的日志 API 可以连接到 OpenTelemetry,避免了对应用程序的重新测量。

每当它出现的时候,日志就会自动附加到当前的追踪中。这使得事务日志很容易找到,并允许自动分析,以找到同一追踪中的日志之间的准确关联。

Baggage

OpenTelemetry Baggage 是一个简单但通用的键值系统。一旦数据被添加为 Baggage(包袱)它就可以被所有下游服务访问。这允许有用的信息,如账户和项目 ID,在事务的后期变得可用,而不需要从数据库中重新获取它们。例如,一个使用项目 ID 作为索引的前端服务可以将其作为 Baggage 添加,允许后端服务也通过项目 ID 对其跨度和指标进行索引。

你可以将 Baggage 看做是一种分布式文本的形式。直接放入上下文对象的项目只能在当前服务中访问。与追踪上下文一样,作为 Baggage 添加的项目被作为 header 注入网络请求,允许下游服务提取它们。

与上下文对象一样,Baggage 本身不是一个可观测性工具。它更像是一个通用的数据存储和传输系统。除了可观测性之外,其他跨领域的工具,例如,功能标记、A/B 测试和认证,可以使用 Baggage 来存储他们需要追踪当前事务的任何状态。

然而,Baggage 是有代价的。因为每增加一个项目都必须被编码为一个头,每增加一个项目都会增加事务中每一个后续网络请求的大小。这就是为什么我们称它为 Baggage。我建议,Baggage 要少用,作为交叉关注的一部分。Baggage 不应该被用作明确定义的服务 API 的 “方便” 替代品,以明确地向下游应用程序发送参数。

OpenTelemetry 客户端架构

应用程序通过安装一系列的软件库来检测 OpenTelemetry:API、SDK(软件开发工具包)、SDK 插件和库检测。这套库被称为 OpenTelemetry 客户端。图 5-2 显示了这些组件之间的关系。

image
图 5-2:OpenTelemetry 客户端架构。为了帮助管理依赖性,OpenTelemetry 将实现与仪表使用的 API 分开。

在许多语言中,OpenTelemetry 提供安装程序,这有助于自动安装和设置 OpenTelemetry 客户端。然而,可用的自动化程度取决于语言。在 Java 中,OpenTelemetry 提供了一个 Java 代理,它通过动态地注入所有必要的组件来实现安装的完全自动化。在 Go 中,OpenTelemetry 包必须通过编写代码来安装和初始化,就像任何其他 Go 包一样。Python、Ruby 和 NodeJS 介于两者之间,提供不同程度的自动化。

在学习 OpenTelemetry 时,了解在你使用的语言中如何设置是很重要的。特别是,一定要学习如何安装仪表,因为不同的语言有很大的不同。

请查看客户端文档,了解更多的入门细节。

客户端架构:仪表 API

OpenTelemetry API 是指用于编写仪表的一组组件。该 API 被设计成可以直接嵌入到开放源码软件库以及应用程序中。这是 OpenTelemetry 的唯一部分,共享库和应用逻辑应该直接依赖它。

提供者(Provider)

API 与任何实现完全分开。当一个应用程序启动时,可以通过为每个信号注册一个提供者来加载一个实现。提供者成为所有 API 调用的接收者。

当没有加载提供者时,API 默认为无操作提供者。这使得 OpenTelemetry 仪表化可以安全地包含在共享库中。如果应用程序不使用 OpenTelemetry,API 调用就会变成 no-ops,不会产生任何开销。

对于生产使用,我们建议使用官方的 OpenTelemetry 提供商,我们称之为 OpenTelemetry SDK

为什么有多个实现方案?

API 和实现的分离有很多好处。但是,如果用户被迫总是安装官方的 OpenTelemetry SDK,这又有什么意义?是否有必要安装另一个实现?SDK 已经具有很强的扩展性。

我们相信是有的。虽然我们希望 OpenTelemetry 仪表是通用的,但建立一个对所有用例都理想的单一实现是不可能的。尽管我们相信 OpenTelemetry SDK 很好,但也应该有一个选择,那就是使用另一种实现。实现的灵活性是提供通用仪表 API 的一个关键特征。

首先,这种分离保证了 OpenTelemetry 不会产生无法克服的依赖冲突。我们总是可以选择加载一个包括不同依赖链的实现。

另一个原因是性能。OpenTelemetry SDK 是一个可扩展的、通用的框架。虽然 SDK 的设计是为了尽可能地提高性能,但扩展性和性能总是要权衡一下的。例如,通过外来函数接口创建与 OpenTelemetry C++ SDK 的绑定,有可能成为 Ruby、Python 和 Node.js 等动态脚本语言的一个非常有效的选择。

还有一些流媒体架构显示了有希望的性能提升。在许多这样的优化解决方案中,编写插件和生命周期钩子的能力将受到严重限制;支持这些类型的功能所需的数据结构在这些优化解决方案中是不存在的。归根结底,没有 “完美的实现”;只有权衡。

API/SDK 的分离是一个关键的设计选择,该项目大量使用了这一点。例如,除了 SDK 之外,每一种语言都有一个 no-op 的实现,它是默认安装的。还有一个 Fake/Mock 实现,我们用它来测试。而且,还有可能实现更多创造性的实现。例如,为分布式系统建立开发者工具,如一个实时调试器,它可以跨越网络边界工作。

客户端架构:SDK

OpenTelemetry 项目为 OpenTelemetry API 提供了一个官方实现,我们称之为 OpenTelemetry SDK。该 SDK 通过提供一个插件框架来实现 OpenTelemetry API。下面将介绍追踪 SDK;类似的架构也适用于度量和日志。

基本数据结构是一个无锁的 SpanData 对象。当用户开始一个跨度时,SpanData 对象被创建,当用户添加属性和事件时,它被自动建立起来。一旦一个跨度结束,SpanData 对象将不再被更新,可以安全地传递给后台线程。

SDK 的插件架构被组织成一个流水线。对于追踪来说,该管道由一连串的 SpanProcessors 组成。每个处理器对 SpanData 对象进行两次同步访问:一次是在跨度开始时,另一次是在跨度结束后。采样器、日志附加器和数据清洗器是 SpanProcessors 的例子。链中的最后一个处理器通常是一个 BatchSpanProcessor,它管理着一个已完成的跨度的缓冲区。输出器可以连接到 BatchSpanProcessor,通过网络将成批的跨度传递到遥测管道中的下一个服务,通常将它们发送到收集器或直接发送到追踪后端。一旦跨度被导出,管道就完成了,SpanData 对象也被释放。

采样器(Sampler)

OpenTelemetry 提供了几种常见的采样算法,包括前期采样和基于优先级的采样。采样可以帮助控制成本,但它是有代价的:你将会错过数据。在启用任何种类的采样算法之前,重要的是要检查你计划使用的分析工具支持哪些类型的采用。意外的采样可能会破坏某些形式的分析。一些工具需要他们自己的采样插件。例如,AWS X-Ray 使用它自己的采样算法,它可以作为 AWS 特定的采样插件使用。

导出器(Exporter)

OpenTelemetry 为 OTLP(OpenTelemetry Protocol)、Jaeger、Zipkin、Prometheus 和 StatsD 提供导出器。由第三方维护的其他导出器可以在每种语言的 OpenTelemetry-Contrib 资源库中找到。使用 OpenTelemetry 注册表来了解目前有哪些插件可用。

客户端架构:库仪表化

为了正常工作,OpenTelemetry 需要端到端的工具。这不是可有可无的:如果关键的库不包括仪表,上下文传播将被破坏。

一般来说,必须检测的库包括 HTTP 客户端、HTTP 服务器、应用框架、消息传递 / 队列系统和数据库客户端。这些库经常在上下文传播中起作用。

  • HTTP 客户端必须创建一个客户端 span 来记录请求。客户端还必须使用一个传播器,将当前的上下文作为一组 HTTP 头信息注入到请求中。
  • HTTP 服务器(应用框架)必须使用一个传播器来从 HTTP 头信息中提取上下文。提取的上下文被用来创建一个服务器跨度,该跨度被设置为当前活动的跨度,它封装了所有的应用程序代码。
  • 同样,消息 / 队列系统中的发送者必须使用传播器将上下文注入消息中,这样就可以通过在接收者身上提取上下文来继续追踪。
  • 数据库客户必须创建一个数据库跨度来记录数据基础事务。一旦数据库服务器也使用 OpenTelemetry 工具,数据库客户端也必须将上下文注入数据库请求中。

这一要求是我们希望看到开放源码软件库能带有本地仪表的主要原因之一。同时,仪表插件是由 OpenTelemetry 项目或第三方提供的 contrib 包。

收集器(Collector)

除了上述的客户端外,OpenTelemetry 还提供了一个独立的服务,称为收集器。收集器是一个灵活、可配置的遥测处理系统。其基本结构如图 5-3 所示。

image
图 5-3:收集器有一个可配置的处理管道,可以导入和导出许多常见格式的数据。

收集器管道可以提供以下服务:

  • 配置,如路由和数据导出格式。OpenTelemetry 客户端可用的几乎所有配置选项都可以在收集器中进行管理。
  • 数据处理,如刷新、格式转换和向多个目的地发送。
  • 缓冲,帮助管理网络。
  • 机器级环境的资源检测。可以发现主机、Kubernetes 和云提供商的细节,并将其附加到收集器收到的所有数据上。
  • 收集主机指标,如 RAM、CPU 和存储容量。

运维人员可以使用收集器来管理与可观测系统相关的所有部署细节,而不需要与应用程序本身进行交互。由于大多数配置选项是特定于部署的,而且是由运维而不是应用程序开发人员管理的,因此,将遥测配置从应用程序转移到收集器,可以干净地分离关注点。

如果所有的路由和数据处理任务都转移到收集器上,OpenTelemetry SDK 就可以以更简单的配置运行。默认情况下,SDK 将发送未处理的 OTLP 数据到一个预定的本地端口,在那里它可以由本地收集器接收。

收集器架构:接收器(Receiver)

收集器可以被配置为从各种来源接收各种格式的遥测数据。目前,收集器支持超过四十种不同类型的接收器!一旦接收到,所有这些数据都会被转换为 OTLP。OpenTelemetry 同时支持基于推和拉的接收器。

收集器架构:处理器(Processor)

一旦接收器将遥测数据转换为 OTLP,就会有各种处理器可用。处理器可以被配置为执行各种任务。

  • 清洗数据以删除敏感数据,如 PII(个人身份信息)。
  • 数据规范化,例如将数据源的旧版本转换为与当前后台使用的仪表盘和查询相匹配的版本。
  • 根据某些属性将数据路由到特定的后端。例如,将与欧盟用户有关的数据存储在欧盟境内托管的存储系统上。
  • 基于尾部的采样,以帮助确保错误和异常值更有可能被捕获,同时对嘈杂和无趣的信息进行速率限制。

收集器架构:导出器(Exporter)

一旦遥测数据被处理,它可以被输出到各种后端。在未来,我们希望越来越多的后端能够原生支持 OTLP。同时,OTLP 可以被转换为目前流行的系统所支持的许多格式。请查看 OpenTelemetry 供应商页面,找到目前支持 OpenTelemetry 的商业供应商列表。

除了将遥测数据转换为单一格式外,还可以安装多个导出器。遥测数据可以按类型分开,并发送到不同的后端。例如,将追踪数据发送到 Jaeger,将度量数据发送到 Prometheus。

重复的遥测数据也可以被同步发送到多个后端。这使得运维人员可以从一个后端无缝切换到另一个后端,而不会有任何服务上的损失。它还允许特殊的分析工具与通用的观测平台一起接收数据。

收集器架构:管道(Pipeline)

收集器允许接收器、处理器和导出器组合成复杂的管道(pipeline),可以同时运行。管道是通过 YAML 配置文件设计和管理的。

这种配置语言是非常强大的。通过在每台机器上部署一个本地收集器,并将它们连接到配置为执行专门处理任务的几层收集器部署上,收集器可以用来开发一个大规模的、强大的遥测系统。