无论您是否实现微服务,您的系统很可能由多个组件组成。最直接的系统可能由反向代理、应用程序和数据库组成。在这种情况下,监控不仅是一个好主意,而且是一项要求。请求可能流经的组件数量越多,要求就越强。

然而,监控只是旅程的开始。当请求开始集体失败时,您需要一个跨所有组件的聚合视图。它被称为追踪,它是可观察性的支柱之一。另外两个是指标和日志。

在这篇文章中,我将只关注跟踪,并描述如何开始你的可观察性之旅。

W3C 跟踪上下文规范

跟踪解决方案应提供跨异构技术堆栈工作的标准格式。这种格式需要遵守规范,无论是正式的还是事实上的。

人们需要了解,规范很少会凭空出现。一般来说,市场已经有几个不同的实现。大多数时候,一个新的规范会导致一个额外的实现,正如著名的 XKCD 漫画所描述的:

然而,有时会发生奇迹:市场遵守新规范。在这里,Trace Context 是一个 W3C 规范,它似乎成功了:


“该规范定义了标准的 HTTP 标头和一种值格式来传播支持分布式跟踪场景的上下文信息。该规范标准化了上下文信息在服务之间的发送和修改方式。上下文信息唯一地标识了分布式系统中的各个请求,还定义了一种方法添加和传播提供者特定的上下文信息。”

文件中出现了两个关键概念:

  • 跟踪遵循跨越多个组件的请求的路径。

  • 跨度绑定到单个组件并通过子父关系链接到另一个跨度。

在撰写本文时,该规范是 W3C 推荐,这是最后阶段。

Trace Context 已经有很多实现。其中之一是 OpenTelemetry。

OpenTelemetry 作为黄金标准

您越接近 IT 的运营部分,您听说OpenTelemetry的机会就越高:

“OpenTelemetry 是工具、API 和 SDK 的集合。使用它来检测、生成、收集和导出遥测数据(指标、日志和跟踪),以帮助您分析软件的性能和行为。

OpenTelemetry 通常可用于多个语言,适合使用。”

OpenTelemetry 是一个由CNCF管理的项目。在 OpenTelemetry 之前有两个项目:

  • OpenTracing,顾名思义就是专注于trace

  • OpenCensus,其目标是管理指标和跟踪

两个项目合并并在顶部添加了日志。OpenTelemetry 现在提供了一组专注于可观察性的“层”:

  • 多种语言的检测 API

  • 规范的实现,同样用不同的语言

  • 基础设施组件,例如收集器

  • 互操作性格式,例如 W3C 的 Trace Context

请注意,虽然 OpenTelemetry 是一个 Trace Context 实现,但它的功能更多。Trace Context 将自身限制为 HTTP,而 OpenTelemetry 允许 span 跨非 Web 组件,例如 Kafka。它超出了这篇博文的范围。

用例

我最喜欢的用例是电子商务商店,所以我们不要更改它。在这种情况下,商店是围绕微服务设计的,每个微服务都可以通过 REST API 访问,并受到 API 网关的保护。为了简化博客文章的架构,我将只使用两个微服务:catalog管理产品和pricing处理产品价格。

当用户到达应用程序时,主页会获取所有产品,获取它们各自的价格并显示它们。

更有趣的是,catalog是一个用 Kotlin 编码的 Spring Boot 应用程序,而pricing一个 Python Flask 应用程序。

跟踪应该允许我们通过网关跟踪请求的路径,包括微服务和(如果可能的话)数据库。

网关处的痕迹

入口点是跟踪中最令人兴奋的部分,因为它应该生成跟踪 ID。在这种情况下,入口点是网关。我将使用Apache APISIX来实现演示:

“Apache APISIX 提供了丰富的流量管理功能,如负载均衡、动态上游、金丝雀发布、断路器、身份验证、可观察性等。”

Apache APISIX 基于插件架构并提供OpenTelemetry 插件:

“该opentelemetry插件可用于根据 OpenTelemetry 规范报告跟踪数据。

该插件仅支持基于 HTTP 的二进制编码 OLTP。”

让我们配置opentelemetry插件:

#1:在独立模式下运行 Apache APISIX 以使演示更易于理解。无论如何,这在生产中是一个很好的做法。

#2:配置opentelemetry为全局插件。

#3:设置服务的名称。它将出现在跟踪显示组件中的名称。

#4:将跟踪发送到jaeger服务。以下部分将对其进行描述。

我们想跟踪每条路由,所以我们应该将插件设置为全局插件,而不是向每条路由添加插件:

#1:跟踪对性能有影响。我们追踪的越多,我们的影响就越大。因此,我们应该仔细平衡性能影响与可观察性的好处。但是,对于演示,我们希望跟踪每个请求。

收集、存储和显示轨迹

虽然 Trace Context 是 W3C 规范,而 OpenTelemetry 是事实上的标准,但市场上存在许多收集、存储和显示跟踪的解决方案。每个解决方案都可以提供所有三种功能或仅提供其中的一部分。例如,Elastic 堆栈处理存储和显示,但您必须依靠其他东西来收集。另一方面,Jaeger和Zipkin确实提供了一个完整的套件来实现所有三个功能。

Jaeger 和 Zipkin 早于 OpenTelemetry,因此每个都有其跟踪传输格式。不过,它们确实提供了与 OpenTelemetry 格式的集成。

在这篇博文的范围内,确切的解决方案并不相关,因为我们只需要功能。我选择 Jaeger 是因为它提供了一个一体化的 Docker 镜像:每个功能都有其组件,但它们都嵌入在同一个镜像中,这使得配置更加轻松。

镜像的相关端口如下:

Docker Compose 位如下所示:

#1:使用all-in-one图像。

#2:非常重要:启用 OpenTelemetry 格式的收集器。

#3:暴露 UI 端口。

现在我们已经建立了基础设施,我们可以专注于在我们的应用程序中启用跟踪。

Flask 应用程序中的跟踪

该pricing服务是一个简单的Flask应用程序。它提供了一个端点来从数据库中获取单个产品的价格。

#1:端点

#2:路线需要产品的ID。

#3:使用 SQLAlchemy 从数据库中获取数据。

#4:真实定价引擎永远不会随着时间的推移返回相同的价格。让我们将价格随机化一点以取乐。

警告:每次调用获取单一价格是非常低效的。它需要与产品一样多的调用,但它提供了更令人兴奋的跟踪。在现实生活中,路由应该能够接受多个产品 ID 并在一个请求-响应中获取所有相关价格。

现在是检测应用程序的时候了。有两种选择:自动仪表和手动仪表。自动是省力和快速的胜利;手册需要专注的开发时间。我建议从自动开始,如果需要,只添加手动。

我们需要添加几个 Python 包:

我们需要配置几个参数:

#1:将跟踪发送给 Jaeger。

#2:设置服务的名称。它将出现在跟踪显示组件中的名称。

#3:我们对日志和指标都不感兴趣。

现在,我们不使用标准flask run命令,而是将其包装:

就这样,我们已经从方法调用和 Flask 路由中收集了 span。

如果需要,我们可以手动添加额外的跨度,例如. :

#1:使用配置的标签和属性添加一个额外的跨度。

Spring Boot 应用程序中的跟踪

该服务是用Kotlin 开发catalog的 Reactive Spring Boot应用程序。它提供了两个端点:

  • 一取单品

  • 另一种是获取所有产品。

两者都先查看产品数据库,然后查询上述pricing服务的价格。

至于 Python,我们可以利用自动和手动检测。让我们从唾手可得的自动化仪表开始。在 JVM 上,我们通过一个代理来实现:

与 Python 一样,它为每个方法调用和 HTTP 入口点创建跨度。它还检测 JDBC 调用,但我们有一个反应式堆栈,因此使用 R2DBC。作为记录,GitHub 问题已开放以添加支持。

我们需要配置默认行为:

#1:将跟踪发送给 Jaeger。

#2:设置服务的名称。它将出现在跟踪显示组件中的名称。

#3:我们对日志和指标都不感兴趣。

至于 Python,我们可以通过添加手动检测来提升游戏。有两个选项可用:程序化和基于注释。除非我们引入 Spring Cloud Sleuth,否则前者有点复杂。让我们添加注释。

我们需要一个额外的依赖:

请注意:该工件是最近从io.opentelemetry:opentelemetry-extension-annotations.

我们现在可以注释我们的代码:

#1:使用配置的标签添加一个额外的跨度。

#2:将参数用作属性,键设置为id,值设置为参数的运行时值。

结果!

我们现在可以使用我们的简单演示来查看结果:

响应并不有趣,但让我们看看 Jaeger UI。我们找到了两条踪迹,每次调用一条:

我们可以深入研究单个跟踪的跨度:

请注意,我们可以在没有上述 UML 图的情况下推断出序列流。更好的是,序列显示组件内部的调用。

每个跨度都包含自动检测添加的属性和我们手动添加的属性:

结论

在这篇文章中,我展示了跟踪跨 API 网关的请求、基于不同技术堆栈的两个应用程序以及它们各自的数据库。我只刷了跟踪的表面:在现实世界中,跟踪可能会涉及与 HTTP 无关的组件,例如 Kafka 和消息队列。

尽管如此,大多数系统还是以一种或另一种方式依赖 HTTP。虽然设置起来并不简单,但也不是太难。跨组件跟踪 HTTP 请求是您实现系统可观察性之旅的良好开端。