2022 年 9 月 5 日

使用 OpenTelemetry 和 Prisma 追踪来监控你的服务器

追踪是一个强大的工具,可以让你分析应用程序的性能并识别瓶颈。本教程将教你追踪的核心概念,以及如何使用 OpenTelemetryPrisma 的追踪功能将追踪集成到你的应用程序中。

Monitor Your Server with Tracing Using OpenTelemetry & Prisma

目录

简介

在本教程中,你将学习如何将追踪集成到使用 Prisma 和 Express 构建的现有 Web 应用程序中。你将使用 OpenTelemetry 实现追踪,这是一个供应商中立的标准,用于收集追踪和其他遥测数据(例如,日志、指标等)。

最初,你将为 HTTP 端点创建手动追踪并将它们打印到控制台。然后,你将学习如何使用 Jaeger 可视化你的追踪。你还将学习如何使用 Prisma 的追踪功能自动生成数据库查询的追踪。最后,你将了解自动检测以及使用追踪时的性能考虑因素。

什么是追踪?

追踪是一种*可观测性工具*,它记录请求通过你的应用程序传播时所采取的路径。追踪可以帮助你将系统响应于任何特定请求所执行的活动链接起来。追踪还提供有关这些活动的时间信息(例如,开始时间、持续时间等)。

单个*追踪*可以让你了解用户或应用程序发出请求时发生的情况。每个追踪由一个或多个 *span* 组成,其中包含有关在请求期间发生的单个步骤或任务的信息。

使用诸如 Jaeger 之类的追踪工具,追踪可以可视化为如下所示的图表

Visualization of a single trace

单个 span 可以有多个子 span,这些子 span 代表在父 span 期间发生的子任务。例如,在上图中,PRISMA QUERY span 具有名为 PRISMA ENGINE 的子 span。最顶层的 span 称为*根 span*,表示从开始到结束的整个追踪。在上图中,GET /ENDPOINT 是根 span。

追踪是更深入地了解和观察你的系统的好方法。它可以让你准确地识别影响你的应用程序的错误和性能瓶颈。追踪对于调试分布式系统尤其有用,在分布式系统中,每个请求可能涉及多个服务,并且特定问题可能难以在本地重现。

注意: 追踪通常与 指标 结合使用,以更好地观察你的系统。要了解有关指标的更多信息,请查看我们的 指标教程

你将使用的技术

在本教程中,你将使用以下工具

先决条件

假定的知识

这是一个对初学者友好的教程。但是,本教程假设

  • JavaScript 或 TypeScript(首选)的基本知识
  • 后端 Web 开发的基本知识

注意:本教程假设没有关于追踪和可观测性的先验知识。

开发环境

要学习本教程,你将需要

  • ... 安装了 Node.js
  • ... 安装了 DockerDocker Compose
  • ... *可选* 安装了 Prisma VS Code 扩展。Prisma VS Code 扩展为 Prisma 添加了一些非常好的 IntelliSense 和语法高亮显示。
  • ... *可选* 能够访问 Unix shell(例如 Linux 和 macOS 中的终端/shell)来运行本系列中提供的命令。

如果你没有 Unix shell(例如,你使用的是 Windows 机器),你仍然可以继续学习,但可能需要修改 shell 命令以适合你的机器。

克隆仓库

你将需要一个 Web 应用程序来演示追踪。你可以使用我们为此教程构建的现有 Express Web 应用程序。

要开始,请执行以下操作

  1. 克隆 仓库
  1. 导航到克隆的目录
  1. 安装依赖
  1. prisma/migrations 目录应用数据库迁移

注意:此命令还将生成 Prisma 客户端并种子数据库。

  1. 启动项目

注意:在开发应用程序时,你应该保持服务器运行。 dev 脚本应在代码发生任何更改时重新启动服务器。

该应用程序只有一个端点:http://localhost:4000/users/random。此端点将从数据库返回 10 个用户的随机样本。通过访问上面的 URL 或运行以下命令来测试端点

项目结构和文件

你克隆的仓库具有以下结构

此仓库中值得注意的文件和目录是

  • prisma
    • schema.prisma:定义数据库模式。
    • migrations:包含数据库迁移历史记录。
    • seed.ts:包含一个脚本,用于使用虚拟数据种子你的开发数据库。
    • dev.db:存储 SQLite 数据库的状态。
  • server.ts:带有 GET /users/random 端点的 Express 服务器。
  • tsconfig.json & package.json:配置文件。

将追踪集成到你的应用程序中

你的 Express 应用程序已经实现了所有的核心“业务逻辑”(例如,返回 10 个随机用户)。为了衡量性能并提高应用程序的可观测性,你将集成追踪功能。

在本节中,你将学习如何初始化追踪并手动创建追踪。

初始化追踪

你将使用 OpenTelemetry 追踪来实现追踪。OpenTelemetry 提供了一个开源实现,它兼容各种平台和语言。此外,它还提供了库和 SDK 来实现追踪。

首先安装以下 OpenTelemetry 包来开始追踪

这些包包含了 OpenTelemetry 追踪的 Node.js 实现。

现在,创建一个新的 tracing.ts 文件来初始化追踪

tracing.ts 中,按如下方式初始化追踪

initializeTracing 函数做了以下几件事

  1. 它初始化了一个 tracer provider (追踪器提供者),用于创建 tracers (追踪器)。追踪器在你的应用程序内部创建追踪/span (跨度)。
  2. 它定义了一个 trace exporter (追踪导出器),并将其添加到你的 provider 中。追踪导出器将追踪发送到各种目的地。在这种情况下,ConsoleSpanExporter 将追踪打印到控制台。
  3. 它通过调用 .register() 函数,注册该 provider 以便与 OpenTelemetry API 一起使用。
  4. 最后,它创建一个追踪器并返回,该追踪器具有作为参数传递给函数的给定名称。

现在,在现有的 server.ts 中导入并调用 initializeTracing

现在你可以创建你的第一个追踪了!

创建你的第一个追踪

在前一节中,你初始化了追踪并将追踪器导入到你的服务器。现在你可以使用 tracer 对象在你的服务器内部创建 span。首先,你将创建一个追踪,封装 GET /users/random 请求。按如下方式更新请求处理程序定义

这里你使用 startActiveSpan() 创建一个新的 span,并将所有请求处理程序逻辑封装在它提供的回调函数中。回调函数带有一个对 span 对象的引用,你将其命名为 requestSpan。你可以使用它来修改或向 span 添加属性。在此代码中,你根据请求的结果,将一个名为 http.status 的属性设置为 span。最后,一旦请求被服务,你就会结束该 span。

要查看你新创建的 span,请访问 http://localhost:4000/users/random。或者,你可以在终端中运行以下命令

转到运行 Express 服务器的终端窗口。你应该看到一个与以下内容 *类似* 的对象打印到控制台

此对象表示你刚刚创建的 span。这里一些值得注意的属性是

  • id 表示此特定 span 的唯一标识符。
  • traceId 表示特定追踪的唯一标识符。特定追踪中的所有 span 都将具有相同的 traceId。现在,你的追踪仅包含单个 span。
  • parentId 是父 span 的 id。在这种情况下,它是 undefined,因为根 span 没有父 span。
  • name 表示 span 的名称。这是你在创建 span 时指定的。
  • timestamp 是一个 UNIX 时间戳,表示 span 的创建时间。
  • duration 是 span 的持续时间,以微秒为单位。

使用 Jaeger 可视化追踪

目前,你正在控制台中查看追踪。虽然对于单个追踪来说这是可管理的,但对于大量追踪来说,这不是很有用。为了更好地理解你的追踪,你需要一些可以可视化追踪的追踪解决方案。在本教程中,你将使用 Jaeger 来实现此目的。

设置 Jaeger

你可以通过两种方式设置 Jaeger

在本教程中,你将使用 Docker Compose 来运行 Jaeger 的 Docker 镜像。首先,创建一个新的 docker-compose.yml 文件

在该文件中定义以下服务

运行此镜像将在 Docker 容器内设置并初始化 Jaeger 的所有必要组件。要运行 Jaeger,请打开一个 *新的* 终端窗口,并在项目的根文件夹中运行以下命令

注意:如果关闭运行 docker 容器的终端窗口,它也会停止该容器。如果将 -d 选项添加到命令的末尾,则可以避免这种情况,例如:docker-compose up -d

如果一切顺利,你应该能够在 http://localhost:16686 访问 Jaeger。

Jaeger user interface

由于你的应用程序尚未将追踪发送到 Jaeger,因此 Jaeger UI 将为空。

添加 Jaeger 追踪导出器

要查看 Jaeger 中的追踪,你需要设置一个新的追踪导出器,该导出器会将追踪从你的应用程序发送到 Jaeger(而不是仅仅将它们打印到控制台)。

首先,在你的项目中安装导出器包

现在将导出器添加到 tracing.ts

这里你初始化了一个新的 JaegerExporter 并将其添加到你的追踪器提供者。 JaegerExporter 构造函数中的 endpoint 属性指向 Jaeger 正在侦听追踪数据的位置。你还删除了控制台导出器,因为它不再需要了。

你现在应该能够在 Jaeger 中看到你的追踪。要查看你的第一个追踪

  1. 再次查询 GET /users/random 端点 (curl http://localhost:4000/users/random)。
  2. 转到 http://localhost:16686
  3. 在左侧的 搜索 选项卡中,在 服务 下拉列表中,选择 express-server
  4. 搜索 选项卡的底部附近,单击 查找追踪
  5. 你现在应该看到一个追踪列表。单击列表中的第一个追踪。
  6. 你将看到追踪的详细视图。应该有一个名为 GET /users/random 的 span。单击该 span 以获取更多信息。
  7. 你应该能够看到有关追踪的各种信息,例如 持续时间开始时间。 你还应该看到多个 标签,其中一个是手动设置的 (http.status)。

Viewing traces inside Jaeger

为你的 Prisma 查询添加追踪

在本节中,你将学习如何追踪你的数据库查询。最初,你将通过自己创建 span 来手动执行此操作。即使使用 Prisma 不再需要手动追踪,实现手动追踪将使你更好地理解追踪的工作原理。

然后,你将使用 Prisma 中的新 追踪功能 来自动执行相同的操作。

手动追踪你的 Prisma 查询

要手动追踪你的 Prisma 查询,你必须将每个查询包装在一个 span 中。你可以通过将以下代码添加到你的 server.ts 文件来实现

你已经为 Prisma 查询创建了一个名为 prisma.user.findmany 的新 span。你还对 users 变量的声明方式进行了一些更改,以使其与代码的其余部分保持一致。

通过再次查询 GET /users/random 端点 (curl http://localhost:4000/users/random) 并在 Jaeger 中查看新生成的追踪来测试新的 span。

Child span visualized in Jaeger

你应该看到生成的追踪有一个新的子 span,名为 prisma.user.findmany,嵌套在父 GET /users/random span 下。现在你可以看到请求中花费多少时间执行 Prisma 查询。

手动与自动检测

到目前为止,你已经学习了如何设置追踪并为你的应用程序手动生成追踪和 span。像这样手动定义 span 称为 *手动检测*。手动检测使你可以完全控制如何追踪你的应用程序,但是,它有一些缺点

  • 手动追踪你的应用程序非常耗时,尤其是当你的应用程序很大时。
  • 并非总是可以正确地手动检测第三方库。例如,无法使用手动检测来追踪 Prisma 内部组件的执行。
  • 由于它涉及手动编写大量代码,因此可能导致错误和异常(例如,不正确的错误处理、损坏的 span 等)。

幸运的是,许多框架和库都提供了 *自动检测*,允许你自动为这些组件生成追踪。自动检测几乎不需要更改代码,设置速度非常快,并且可以开箱即用地为你提供基本遥测数据。

重要的是要注意,自动检测和手动检测并非相互排斥。同时使用这两种技术可能是有益的。自动检测可以提供良好的基线遥测数据,并在你所有的端点上实现高覆盖率。然后可以添加手动检测,以用于特定的细粒度追踪和自定义指标/元数据。

为 Prisma 设置自动检测

本节将教你如何使用新的追踪功能为 Prisma 设置自动检测。首先,在你的 schema.prisma 文件的生成器块中启用追踪功能标志

注意: 追踪目前是预览功能。因此,您必须添加 tracing 功能标志才能使用追踪。

现在,重新生成 Prisma Client

要执行自动检测,您还需要使用 npm 安装两个新软件包

需要这些软件包,因为

  • @opentelemetry/instrumentation 需要设置自动检测。
  • @prisma/instrumentation 提供 Prisma Client 的自动检测。

根据 OpenTelemetry 术语,被检测库是指收集追踪的库或包。另一方面,检测库是为特定被检测库生成追踪的库。在本例中,Prisma Client 是被检测库,而 @prisma/instrumentation 是检测库。

现在,您需要使用 OpenTelemetry 注册 Prisma Instrumentation。为此,请将以下代码添加到您的 tracing.ts 文件中

registerInstrumentations 调用接受两个参数

  • instrumentations 接受您要注册的所有检测库的数组。
  • tracerProvider 接受您的追踪器(tracer)的追踪器提供程序。

由于您正在设置自动检测,因此不再需要手动为 Prisma 查询创建 span。通过删除 Prisma 查询的手动 span 来更新 server.ts

使用自动检测时,初始化追踪的顺序很重要。您需要在导入被检测库之前设置追踪并注册检测。在本例中,initializeTracing 调用必须在 PrismaClientimport 语句之前。

再次向 GET /users/random 端点发出请求,并在 Jaeger 中查看生成的追踪。

Visualization of automatic instrumentation with Prisma

这一次,相同的 Prisma 查询会生成多个 span,提供有关查询的更多细粒度信息。启用自动检测后,您添加到应用程序的任何其他查询也会自动生成追踪。

注意:要了解有关 Prisma 生成的 span 的更多信息,请参阅追踪文档的追踪输出部分

为 Express 设置自动检测

目前,您正在通过手动创建 span 来追踪您的端点。与 Prisma 查询一样,随着端点数量的增加,手动追踪将变得难以管理。为了解决这个问题,您还可以为 Express 设置自动检测。

首先安装以下检测库

tracing.ts 内部,注册这两个新的检测库

最后,删除 server.tsGET /users/random 端点的手动 span

GET /users/random 端点发出请求,并在 Jaeger 中查看生成的追踪。

Visualization of automatic instrumentation with Express and Prisma

您应该看到更多的细粒度 span,显示请求通过您的代码的不同步骤。特别是,您应该看到由 ExpressInstrumentation 库生成的新 span,这些 span 显示请求通过各种 Express 中间件和 GET /users/random 请求处理程序。

注意:有关可用检测库的列表,请查看 OpenTelemetry 注册表

减少追踪对性能的影响

如果您的应用程序向收集器(例如 Jaeger)发送大量 span,则可能会对应用程序的性能产生重大影响。这通常在您的开发环境中不是问题,但在生产环境中可能是一个问题。您可以采取一些措施来缓解这种情况。

批量发送追踪

目前,您正在使用 SimpleSpanProcessor 发送追踪。这是低效的,因为它一次发送一个 span。您可以改为使用 BatchSpanProcessor 批量发送 span。

在您的 tracing.ts 文件中进行以下更改,以在生产环境中使用 BatchSpanProcessor

请注意,您仍然在开发环境中使用 SimpleSpanProcessor,在开发环境中,优化性能不是一个大问题。这确保了追踪在开发中生成后立即显示。

通过抽样发送更少的 span

概率抽样是一种技术,允许 OpenTelemetry 追踪用户通过使用随机抽样技术来降低 span 收集性能成本。使用此技术,您可以减少发送到收集器的 span 数量,同时仍然获得应用程序中发生的事情的良好表示。

更新 tracing.ts 以使用概率抽样

与批处理一样,您仅在生产环境中采用概率抽样。

总结和最终说明

恭喜!🎉

在本教程中,您学习了

  • 什么是追踪,以及为什么要使用它。
  • 什么是 OpenTelemetry,以及它与追踪的关系。
  • 如何使用 Jaeger 可视化追踪。
  • 如何将追踪集成到现有的 Web 应用程序中。
  • 如何使用自动检测库来提高代码的可观察性。
  • 如何在生产环境中减少追踪对性能的影响。

您可以在 GitHub 上找到此项目的源代码。如果您发现问题,请随时在存储库中提出 issue 或提交 PR。您也可以在 Twitter 上直接与我联系。

不要错过下一篇文章!

注册 Prisma 新闻通讯