本文是“Microservices June 微服务之月 2023”系列教程的延伸阅读之一,旨在帮助您将概念付诸实践。该系列教程包括:
该系列教程包括:
微服务架构具有诸多优势,包括增强团队自主性、提高扩展与部署灵活性。但缺点是,系统中的服务越多(一个微服务应用可能包含几十个甚至几百个服务),清晰地了解系统总体运行情况的难度就越大。作为复杂软件系统的编写者和维护者,我们深知掌握系统运行情况的重要性。可观测性工具可帮助我们清晰地了解众多服务和支持基础设施。
在本教程中,我们将重点介绍微服务应用的一种非常重要的可观测性工具:链路追踪(tracing)。在步入正题之前,让我们先定义一下讨论可观测性时通常会用到的一些术语:
我们可借助所有这些概念来了解微服务的性能。链路追踪是可观测性策略中特别有用的部分,因为链路追踪提供了发出请求时多个通常松散耦合的组件之间的“全局视图”。这也是识别性能瓶颈的一种尤为高效的方法。
本教程使用了来自 OpenTelemetry (OTel) 的链路追踪工具套件,OTel 是一套用于收集、处理并导出遥测数据的厂商中立的开源标准,正快速被广泛采用。在 OTel 的概念中,链路追踪将一个可能包括多个服务的数据流分成了一系列按时间顺序排列的数据块,以便于您理解:
如果您尚不熟悉 OTel,请参阅《什么是 OpenTelemetry?》以深入了解相关实施标准和注意事项。
本教程主要介绍了如何利用 OTel 来跟踪微服务应用的操作。在本教程的四个挑战中,您将学习如何跟踪通过系统的请求并解决有关自身微服务的问题:
以下挑战介绍了在首次设置链路追踪时我们推荐使用的流程。具体步骤:
注:我们旨在通过本教程阐释一些有关遥测的核心概念,而非展示如何在生产环境中正确部署微服务。虽然我们会用到真正的“微服务”架构,但需要做以下几点说明:
下图显示了本教程中所用的微服务及其他元素之间的整体架构和数据流。
两种微服务如下:
三种支持基础架构包括:
现在暂时把 OTel 搁置脑后,重点介绍下我们正在跟踪的事件序列,即当用户发送新的聊天消息并且接收者收到相关通知时会发生什么。
流程分解如下:
与此同时:
在设置遥测工具时,最好先确定一组明确的监测目标,而不是“发送所有内容,希望获得洞察”。在本教程中,我们有三个主要的遥测目标:
请注意,这些目标与系统的技术操作和用户体验有关。
若在自己的环境中完成本教程的学习,您需要:
一个兼容 Linux/Unix 的环境
注:由于 NGINX 的 OpenTelemetry 模块不兼容(包括 Linux aarch64 架构和搭载 M1 或 M2 芯片的苹果设备),本教程中涉及跟踪 NGINX 的活动不能在基于 ARM 的处理器上工作。涉及信使和通知器服务的活动适用于所有架构。
bash
(本教程会提供并解释所有代码和命令,因此即使您知识有限也无妨)Node.js 19.x 或更高版本
curl
(已安装在大多数系统上)注:本教程用到了 JavaScript SDK,因为信使和通知器服务均使用 Node.js 编写而成。您还需要设置 OTel 自动埋点特性,以便了解 OTel 提供的信息类型。本教程介绍了有关 OTel Node.js SDK 的常用信息,如欲了解更多详情,请参阅 OTel 文档。
在主目录下,创建 microservices-march 目录,并将本教程会用到的 GitHub 代码库复制到其中。(您也可以使用其他目录名称,相应修改指令即可)。
注:本教程中省略了 Linux 命令行提示符,以便您将命令复制和粘贴到终端。波浪符 (~
) 表示您的主目录。
mkdir ~/microservices-marchcd ~/microservices-march
git clone https://github.com/microservices-march/messenger --branch mm23-metrics-start
git clone https://github.com/microservices-march/notifier --branch mm23-metrics-start
git clone https://github.com/microservices-march/platform --branch mm23-metrics-start
在这个挑战中,启动信使服务并配置 OTel 自动埋点以将遥测数据发送至控制台。
切换到平台 platform 代码库并启动 Docker Compose:
cd ~/microservices-march/platformdocker compose up -d --build
这将同时启动 RabbitMQ 和 Jaeger——两者将在后面的挑战中用到。
‑d
标记指示 Docker Compose 在容器启动时与之分离(否则容器将始终与您的终端保持连接)。--build
标记指示 Docker Compose 在启动时重建所有镜像。这可确保您正在运行的镜像通过任何潜在的文件变更保持更新。切换到信使 messenger 代码库的 app 目录并安装 Node.js(您也可以按需采用其他替代方法):
cd ~/microservices-march/messenger/appasdf install
安装依赖项:
npm install
为信使服务启动 PostgreSQL 数据库:
docker compose up -d
创建数据库模式和表格,并插入一些种子数据:
npm run refresh-db
借助 OTel 自动埋点,无需修改信使代码库中的任何内容即可设置链路追踪。所有链路追踪配置并非直接编写在应用代码中,而是在脚本中定义,然后在运行时把脚本导入 Node.js 进程。
此处,您可以配置信使服务的自动埋点使用最基本的链路追踪目标位置,即控制台。在挑战 2 中,您需要更改配置,将链路追踪发送到作为外部收集器的 Jaeger。
仍然在 messenger 代码库的 app 目录下操作,安装核心 OTel Node.js 包:
npm install @opentelemetry/sdk-node@0.36.0 \
@opentelemetry/auto-instrumentations-node@0.36.4
下列库提供了以下功能:
@opentelemetry/sdk-node
——生成并导出 OTel 数据@opentelemetry/auto-instrumentations-node
——使用所有常见 Node.js 埋点的默认配置进行自动设置注:OTel 的不同之处在于,其 JavaScript SDK 被分解成了多个微小部分。因此,在本教程的基本示例中,您将需要多安装几个安装包。如欲了解除本教程中所列以外还需要哪些安装包才能完成监测任务,请仔细阅读(强推)OTel 入门指南并查看 OTel GitHub 代码库。
新建一个名为 tracing.mjs 的文件,添加 OTel 链路追踪的设置和配置代码:
touch tracing.mjs
在您常用的文本编辑器中,打开 tracing.mjs 并添加下列代码:
//1
import opentelemetry from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
//2
const sdk = new opentelemetry.NodeSDK({
traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
instrumentations: [getNodeAutoInstrumentations()],
});
//3
sdk.start();
这些代码会完成以下操作:
新建 NodeSDK 实例并对其进行配置,以便:
ConsoleSpanExporter
)。将自动埋点用作基本埋点组。该埋点加载了所有最常见的自动埋点库。本教程用到了以下库:
@opentelemetry/instrumentation-pg
库 (pg
)@opentelemetry/instrumentation-express
@opentelemetry/instrumentation-amqplib
库 (amqplib
)启动信使服务,导入您在第三步中创建的自动埋点脚本。
node --import ./tracing.mjs index.mjs
稍后,控制台(您的终端)中开始显示大量与链路追踪相关的输出:
...
{
traceId: '9c1801593a9d3b773e5cbd314a8ea89c',
parentId: undefined,
traceState: undefined,
name: 'fs statSync',
id: '2ddf082c1d609fbe',
kind: 0,
timestamp: 1676076410782000,
duration: 3,
attributes: {},
status: { code: 0 },
events: [],
links: []
}
...
注:让终端会话保持打开状态,以便在挑战 2 中使用。
您可以使用许多工具来查看和分析链路追踪,但本教程使用的是 Jaeger。Jaeger 是一个简单的开源端到端分布式链路追踪框架,内置一个基于 Web 的用户界面,用于查看 span 及其他链路追踪数据。平台代码库中提供的基础架构包括 Jaeger(已在挑战 1 的第一步中启动),因此您可以专注于分析数据,不用考虑工具问题。
您可通过在浏览器中访问 http://localhost:16686 端点来访问 Jaeger,但如果您现在就访问该端点,不会看到与您的系统有关的任何内容。这是因为您目前收集的链路追踪正被发送到控制台!如欲在 Jaeger 中查看链路追踪数据,需使用 OpenTelemetry 协议 (OTLP) 格式导出链路追踪。
在这个挑战中,您需要为以下服务配置埋点以监测核心用户流:
值得一提的是,使用 OTel 自动埋点意味着您无需修改信使代码库中的任何内容即可设置链路追踪。但所有链路追踪配置都位于在运行时被导入 Node.js 进程的脚本中。此处,您可将由信使服务生成的链路追踪的目标位置从控制台更改为外部收集器(在本教程中为 Jaeger)。
仍然在与挑战 1 中相同的终端下操作,在信使代码库的 app 目录下,安装 OTLP 输出器 Node.js 包:
npm install @opentelemetry/exporter-trace-otlp-http@0.36.0
@opentelemetry/exporter-trace-otlp-http
库通过 HTTP 导出 OTLP 格式的链路追踪信息,用于向 OTel 外部收集器发送遥测数据。
打开 tracing.mjs(已在挑战 1 中创建和编辑),并进行以下修改:
将下列一行添加到文件顶部的 import
语句中:
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
将提供给 OTel SDK 的“导出器”从挑战 1 中所用的控制台导出器更改为可通过 HTTP 向兼容 OTLP 的收集器发送 OTLP 数据的导出器。将
traceExporter:new opentelemetry.tracing.ConsoleSpanExporter(),
替换为:
traceExporter: new OTLPTraceExporter({ headers: {} }),
注:为了简单起见,本教程假设收集器位于默认位置 http://localhost:4318/v1/traces。在真实系统中,最好明确设置该位置。
按下 Ctrl+c
停止信使服务,该终端是在配置 OTel 自动埋点发送到控制台的第四步中启动的。重启该服务,以使用在第二步中配置的新导出器:
^cnode --import ./tracing.mjs index.mjs
开启第二个终端会话。(在后面的指令中称其为客户端终端,在第一步和第三步中使用的原始终端被称作信使终端。)等待大约十秒钟,然后向信使服务发送健康检查请求(如要查看多个链路追踪,则可多运行几次):
curl -X GET http://localhost:4000/health
在发送请求前等待 10 秒钟有助于您的跟踪更容易被找到,因为在服务启动时自动埋点会生成许多链路追踪。
在浏览器中,访问 Jaeger 用户界面:http://localhost:16686,并验证 OTLP 导出器是否按预期运行。在标题栏中点击 Search(搜索),从 Service(服务)字段的下拉菜单中选择名称以 unknown_service 开头的服务。点击 Find Traces(查找追踪)按钮:
点击窗口右侧的链路追踪,以显示其中的 span 列表。每个 span 都描述了作为链路追踪的一部分运行的各项操作,有时涉及多个服务。截图中的 jsonParser span 显示了运行信使服务的请求处理代码的 jsonParser
部分所用的时长。
正如第五步中所述,OTel SDK 导出的服务名称 (unknown_service) 毫无意义。要想解决这个问题,在信使终端按下 Ctrl+c
来停止信使服务。 然后再安装几个 Node.js 包:
^c
npm install @opentelemetry/semantic-conventions@1.10.0 \
@opentelemetry/resources@1.10.0
下列两个库提供以下功能:
@opentelemetry/semantic-conventions
——定义了 OTel 规范中所定义的链路追踪的标准属性。@opentelemetry/resources
——定义一个对象(资源)以代表生成 OTel 数据的来源(在本教程中为信使服务)。在文本编辑器中打开 tracing.mjs 并进行以下修改:
将下列行添加到文件顶部的 import
语句中:
import { Resource } from "@opentelemetry/resources";import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
在 OTel 规范中的正确键值下创建一个名为 messenger
的 resource
,具体方法是在最后一个 import
语句后添加以下行:
const resource = new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "messenger",
});
通过在以下黑色行之间添加橙色高亮显示的行,将 resource
对象传递给 NodeSDK 构造函数:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [getNodeAutoInstrumentations()],
});
重启信使服务:
node --import ./tracing.mjs index.mjs
等待大约十秒钟,然后在客户端终端中(已在第四步中打开)向服务器发送另一健康检查请求(如欲查看多个链路追踪,则可多运行几次命令):
curl -X GET http://localhost:4000/health
注:让客户端终端保持打开状态,以便在下一部分中再次使用,同时让信使终端保持打开状态,以在挑战 3 中再次使用。
确认一个名为 messenger(信使)的新服务出现在浏览器的 Jaeger 用户界面中(这可能需要几秒钟的时间,而且您可能需要刷新 Jaeger 用户界面):
从 Service 下拉菜单中选择 messenger,然后点击 Find Traces 按钮,即可查看由信使服务生成的所有最新链路追踪(截图显示了 20 条信息中的两条最新信息):
点击一个追踪,以显示其中的 span。每个 span 都被正确地标记为源自于信使服务:
现在为通知器服务启动并配置自动埋点,运行与前两部分中信使服务基本相同的命令。
开启新的终端会话(在后续步骤中被称为通知器终端)。切换到通知器代码库的 app 目录并安装 Node.js(您也可以按需采用其他替代方法):
cd ~/microservices-march/notifier/appasdf install
安装依赖项:
npm install
为通知器服务启动 PostgreSQL 数据库:
docker compose up -d
创建数据库模式和表格,并插入一些种子数据:
npm run refresh-db
安装 OTel Node.js 包(关于这些包功能的描述,请参阅“配置 OTel 自动埋点发送到控制台”中的第一步和第三步):
npm install @opentelemetry/auto-instrumentations-node@0.36.4 \
@opentelemetry/exporter-trace-otlp-http@0.36.0 \
@opentelemetry/resources@1.10.0 \
@opentelemetry/sdk-node@0.36.0 \
@opentelemetry/semantic-conventions@1.10.0
新建一个名为 tracing.mjs 的文件:
touch tracing.mjs
在您常用的文本编辑器中,打开 tracing.mjs 并添加以下脚本,以启动并运行 OTel SDK:
import opentelemetry from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
const resource = new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: "notifier",
});
const sdk = new opentelemetry.NodeSDK({
resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
注:本脚本与信使(notifier)服务所用的完全相同,唯一不同之处是SemanticResourceAttributes.SERVICE_NAME
字段中的值是 notifier
。
使用 OTel 自动埋点启动通知器服务:
node --import ./tracing.mjs index.mjs
等待大约十秒钟,然后在客户端终端向通知器服务发送健康检查请求。该服务将监听端口 5000,以防止与监听端口 4000 的信使服务发生冲突:
curl http://localhost:5000/health
注:让客户端终端和通知器终端保持打开状态,以便在挑战 3 中再次使用。
确认一个名为 notifier 的新服务出现在浏览器的 Jaeger 用户界面中:
对于 NGINX,您需要手动设置链路追踪,而不是使用 OTel 自动埋点方法。目前,使用 OTel 对 NGINX 进行监测的最常见方式是使用用 C 语言编写的模块。第三方模块是 NGINX 生态系统的一个重要组成部分,但需要进行一些设置。本教程向您展示了如何进行这些设置。有关背景信息,请参阅我们的博文《为 NGINX 和 NGINX Plus 编译第三方动态模块》。
开启新的终端会话(NGINX 终端),将目录更改为信使代码库的根目录,并新建一个名为 load-balancer 的目录及三个名为 Dockerfile、nginx.conf 和 opentelemetry_module.conf 的文件:
cd ~/microservices-march/messenger/mkdir load-balancer
cd load-balancer
touch Dockerfile
touch nginx.conf
touch opentelemetry_module.conf
在您常用的文本编辑器中,打开 Dockerfile 并添加以下内容(注释解释了每一行代码的功能,即便您不完全理解这些注释,也能构建和运行 Docker 容器):
FROM --platform=amd64 nginx:1.23.1
# 用我们自己的文件替换 nginx.conf 文件
COPY nginx.conf /etc/nginx/nginx.conf
# 定义 NGINX OTel 模块的版本
ARG OPENTELEMETRY_CPP_VERSION=1.0.3
# 定义编译和运行 NGINX 时所用共享库的搜索路径
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/opentelemetry-webserver-sdk/sdk_lib/lib
# 1. 下载最新版本的 Consul 模板和 OTEL C++ Web 服务器模块、otel-webserver-module
ADD https://github.com/open-telemetry/opentelemetry-cpp-contrib/releases/download/webserver%2Fv${OPENTELEMETRY_CPP_VERSION}/opentelemetry-webserver-sdk-x64-linux.tgz /tmp
RUN apt-get update \
&& apt-get install -y --no-install-recommends dumb-init unzip \
# 2. 提取模块文件
&& tar xvfz /tmp/opentelemetry-webserver-sdk-x64-linux.tgz -C /opt \
&& rm -rf /tmp/opentelemetry-webserver-sdk-x64-linux.tgz \
# 3. 将‘load_module’指令安装并添加至主 NGINX 配置文件的顶部
&& /opt/opentelemetry-webserver-sdk/install.sh \
&& echo "load_module /opt/opentelemetry-webserver-sdk/WebServerModule/Nginx/1.23.1/ngx_http_opentelemetry_module.so;\n$(cat /etc/nginx/nginx.conf)" > /etc/nginx/nginx.conf
# 4. 复制到 NGINX OTel 模块的配置文件中
COPY opentelemetry_module.conf /etc/nginx/conf.d/opentelemetry_module.conf
EXPOSE 8085
STOPSIGNAL SIGQUIT
打开 nginx.conf 并添加以下内容:
events {}
http {
include /etc/nginx/conf.d/opentelemetry_module.conf;
upstream messenger {
server localhost:4000;
}
server {
listen 8085;
location / {
proxy_pass http://messenger;
}
}
}
这个基本的 NGINX 配置文件指示 NGINX:
注:这与 NGINX 在生产环境中作为反向代理和负载均衡器的实际配置很相似。唯一的主要区别是,upstream
块中 server
指令的参数通常是域名或 IP 地址,而非 localhost
。
打开 opentelemetry_module.conf 并添加以下内容:
NginxModuleEnabled ON;NginxModuleOtelSpanExporter otlp;
NginxModuleOtelExporterEndpoint localhost:4317;
NginxModuleServiceName messenger-lb;
NginxModuleServiceNamespace MicroservicesMarchDemoArchitecture;
NginxModuleServiceInstanceId DemoInstanceId;
NginxModuleResolveBackends ON;
NginxModuleTraceAsError ON;
构建一个包含 NGINX 和 NGINX OTel 模块的 Docker 镜像:
docker build -t messenger-lb .
启动 NGINX 反向代理和负载均衡器的 Docker 容器:
docker run --rm --name messenger-lb -p 8085:8085 --network="host" messenger-lb
在客户端终端,通过 NGINX 反向代理和负载均衡器向信使服务发送健康检查请求(在发送该请求之前无需等待):
curl http://localhost:8085/health
注:让 NGINX 和客户端终端保持打开状态,以便在挑战 3 中再次使用。
在浏览器中,确认新的 messenger-lb 服务与您之前启动的服务一同列在 Jaeger 用户界面中。您可能需要在您的浏览器中重新加载 Jaeger 用户界面。
在架构和用户流中,我们概述了用户流的各个阶段,现在简单回顾一下:
实施遥测的目标是:
在这个挑战中,您将学习如何评估 OTel 埋点生成的链路追踪是否满足上述目标要求。首先,运行系统并创建一些链路追踪数据。然后,检查消息流的链路追踪以及其中分别由 NGINX、信使服务和通知器服务生成的部分。
在客户端终端,设置对话并在两位用户之间发送几条消息:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"participant_ids": [1, 2]}' \
'http://localhost:8085/conversations'
curl -X POST \
-H "User-Id: 1" \
-H "Content-Type: application/json" \
-d '{"content": "This is the first message"}' \
'http://localhost:8085/conversations/1/messages'
curl -X POST \
-H "User-Id: 2" \
-H "Content-Type: application/json" \
-d '{"content": "This is the second message"}' \
'http://localhost:8085/conversations/1/messages'
通知器服务生成如下输出,并显示在通知器终端中:
Received new_message: {"type":"new_message","channel_id":1,"user_id":1,"index":1,"participant_ids":[1,2]}Sending notification of new message via sms to 12027621401
Received new_message: {"type":"new_message","channel_id":1,"user_id":2,"index":2,"participant_ids":[1,2]}
Sending notification of new message via email to the_hotstepper@kamo.ze
Sending notification of new message via sms to 19147379938
在浏览器中打开 Jaeger 用户界面,从 Service 下拉菜单中选择 messenger-lb,然后点击 Find Traces 按钮。 此时界面上会显示一个链路追踪列表,回溯到用户流起初。点击任何链路追踪,即可显示相关详情,如下截图所示:
点击并查看详情。在继续操作之前,考虑一下链路追踪中的信息可如何帮助您实现挑战 3 的简介中提到的监测目标。相关问题包括:
从 NGINX span 开始,父 span 包含 11 个子 span。由于当前的 NGINX 配置非常简单,因此子 span 意义不大,只显示了 NGINX 请求处理生命周期中每个步骤所用的时间。但 父 span(第一个)提供了一些有价值的信息:
在 Tags(标签)下,您能够看到以下属性:
http.method
字段——POST
(在 REST 术语中,这意味着创建)http.status_code
字段——201
(表示创建成功)http.target
字段——conversations/1/messages
(消息端点)综合来看,这三条信息传达的意思是:“向 /conversations/1/messages
发送了一个 POST
请求,响应为 201
(创建成功)”。这与架构和用户流中的第一步和第 4a 步相对应)。
webengine.name
字段显示这是该请求的 NGINX 部分。此外,由于信使和通知器的 span 嵌套在 messenger-lb conversations/1
span 内(如准备好解读链路追踪中的截图所示),您可以判断通过 NGINX 反向代理发送给信使服务的请求是否到达了流程中的所有预期组件。
该信息满足目标要求,因为您可以看到 NGINX 反向代理是流程的一部分。
在标记 messenger-lb 的 span 列表中,查看最新 span(位于列表的底部),以了解请求的 NGINX 部分所用时长。在截图中,span 从 589 微秒 (µs) 开始,持续了 24µs,这意味着整个反向代理操作只花了 613µs——约 0.6 毫秒 (ms)。(当然,当您自己操作本教程步骤时,具体数值会有所不同)。
在这样的设置中,大多数情况下,这些值只是相对于其他度量值而言有用,并随系统而变。不过在本例中,该操作的时长显然不到 5 秒阈值。
该信息满足目标要求,因为您可以看到 NGINX 操作的时长都不曾接近 5 秒。如果消息流中出现了非常缓慢的操作,那一定是后来发生的。
NGINX 反向代理层并不包括任何相关信息,因此您可以转到信使 span。
链路追踪的信使服务部分包含另外 11 个 span。同样,大多数子 span 涉及 Express 框架在处理请求时使用的基本步骤,意义不大。但父 span(第一个)再次提供了一些有价值的信息:
在 Tags 下,您能够看到以下属性:
http.method
字段——POST
(同样在 REST 术语中,这意味着创建)http.route
字段——/conversations/:conversationId/messages
(消息路由)http.target
字段——/conversations/1/messages
(消息端点)该信息满足目标要求,因为从中可以看出信使服务是流程的一部分,而且到达的端点是新的消息端点。
如下截图所示,链路追踪的信使部分开始于 1.28 ms,结束于 36.28ms,总时长为 35ms。其中大部分时间花在解析 JSON(middleware
-
jsonParser
)和连接数据库(pg-pool.connect
和 tcp.connect
)上。
鉴于在消息编写过程中还进行了几次 SQL 查询,因此这也很合理。这反过来表明,您可能需要增加自动埋点配置,以捕获这些查询的用时。(本教程没有用到这个额外的埋点,因此在挑战 4 中,您会手动创建 span,后者可用于打包数据库查询。)
该信息满足目标要求,因为从中可以看出信使操作的时长都不曾接近 5 秒。如果消息流中出现了非常缓慢的操作,那一定是后来发生的。
与 NGINX span 一样,信使 span 不包含这些信息,因此您可以转到通知器 span。
链路追踪的通知器部分只包含两个 span:
chat_queue
process
span——确认通知器服务处理了来自 chat_queue 消息队列的事件pg-pool.connect
span——显示处理完事件后,通知器服务与其数据库建立了某种连接仅凭从这些 span 中获取的信息,无法全面了解每一步。您可以看到通知器服务使用来自队列的事件,但却不知道:
这表明您需要执行以下操作才能充分了解通知器服务流:
通过查看通知器服务 span 的总用时,您可以看到请求在消息流的通知器部分花费了 30.77 ms。但是,由于没有表示整个消息流(向接收者发送通知)“结束”的 span,因此您无法确定消息流这一部分的总用时或操作完成的总用时。
您可以看到通知器服务的 chat_queue
process
开始于 6.12ms,即信使服务的 chat_queue
send
开始(于 4.12ms)后 2ms。
该目标达成了,因为您知道通知器在信使 服务派发事件 2ms 后使用了该事件。与目标 2 不同的是,您不需要知道事件是否已被完全处理,也不需要知道耗时多少便可实现此目标。
根据我们对当前 OTel 自动埋点生成的链路追踪的分析,可以清楚地发现:
许多 span 以当前形式而言毫无用处:
request
handler
以及所有数据库操作的 span。一些中间件 span(例如 expressInit
和 corsMiddleware
)似乎不相关,可以移除。以下服务缺少关键 span:
这意味着,基本埋点满足了最后一个目标要求:
然而,没有足够的信息来实现前两个目标:
在这个挑战中,您将需要根据在挑战 3 中完成的追踪分析,优化 OTel 埋点。其中包括删除不必要的 span, 创建新的自定义 span,并确认通知器服务使用的事件是信使服务生成的事件。
在您常用的文本编辑器中,打开信使代码库的 app 目录下的 tracing.mjs 文件,并在顶部的导入语句列表的末尾添加以下内容:
const IGNORED_EXPRESS_SPANS = new Set([ "middleware - expressInit",
"middleware - corsMiddleware",
]);
这定义了一组 span 名称,这些名称来自于下面 Jaeger 用户界面截图中的 span 列表,但由于无法为该消息流提供有用的信息而将从追踪中删除。您可能决定也不需要该截图中所列的其他 span,并将其添加到 IGNORED_EXPRESS_SPANS
列表中。
将过滤器添加至自动埋点配置,以删除您不需要的 span,具体方法是将下列橙色高亮显示的部分:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [getNodeAutoInstrumentations()],
});
更改为以下行:
const sdk = new opentelemetry.NodeSDK({ resource,
traceExporter: new OTLPTraceExporter({ headers: {} }),
instrumentations: [
getNodeAutoInstrumentations({
"@opentelemetry/instrumentation-express": {
ignoreLayers: [
(name) => {
return IGNORED_EXPRESS_SPANS.has(name);
},
],
},
}),
],
});
getNodeAutoInstrumentations
函数引用了第一步中定义的 span 集,以将这些 span 从 @opentelemetry/instrumentation-express
生成的链路追踪中滤除。换而言之,对于属于 IGNORED_EXPRESS_SPANS
的 span,将 return
语句解析为 true
,同时 ignoreLayers
语句从链路追踪中移除该 span。
在信使终端,按下 Ctrl+c
来停止信使服务。然后重启它。
^cnode --import ./tracing.mjs index.mjs
等待大约十秒钟,然后在客户端终端发送一条新消息:
curl -X POST \ -H "User-Id: 2" \
-H "Content-Type: application/json" \
-d '{"content": "This is the second message"}' \
'http://localhost:8085/conversations/1/messages'
在 Jaeger 用户界面中再检查信使 span。两个 middleware
span — expressInit
和 corsMiddleware
不再显示(您可以将其与挑战 3 中检查链路追踪的信使部分的目标 2 截图进行比较)。
在这一部分中,您将首次接触到应用代码。自动埋点无需更改应用便可生成大量信息,但有些见解必须通过对业务逻辑的特定部分进行监测才能获取。
对于您正在监测的新消息流,一个示例是跟踪向消息接收者发送通知。
打开通知器代码库的 app 目录下的 index.mjs。该文件包含服务的所有业务逻辑。在文件顶部的 import
语句列表的末尾添加以下行:
import { trace } from "@opentelemetry/api";
替换该代码(在文件中的第 91 行左右):
for (let pref of preferences) {
console.log(
`Sending notification of new message via ${pref.address_type} to ${pref.address}`
);
}
用以下代码替换上文:
const tracer = trace.getTracer("notifier"); // 1tracer.startActiveSpan( // 2
"notification.send_all",
{
attributes: {
user_id: msg.user_id,
},
},
(parentSpan) => {
for (let pref of preferences) {
tracer.startActiveSpan( // 3
"notification.send",
{
attributes: { // 4
notification_type: pref.address_type,
user_id: pref.user_id,
},
},
(span) => {
console.log(
`Sending notification of new message via ${pref.address_type} to ${pref.address}`
);
span.end(); // 5
}
);
}
parentSpan.end(); // 6
}
);
新代码会执行以下操作:
tracer
,它是一个全局对象,用于与 OTel 链路追踪进行交互。notification.send_all
的新的父 span,并设置 user_id
属性以识别消息的发送者。notification.send_all
下新建了一个名为 notification.send
的子 span。每个通知都会生成一个新 span。为子 span 设置更多的属性:
notification_type
——短信或电子邮件之一user_id
——通知接收用户的 IDnotification.send
。notification.send_all
。拥有父 span 可确保每个“发送通知”操作都会被报告,即使没有发现用户的通知偏好。
在通知器终端,按下 Ctrl+c
来停止通知器服务。然后重启它:
^cnode --import ./tracing.mjs index.mjs
等待大约十秒钟,然后在客户端终端发送一条新消息:
curl -X POST \ -H "User-Id: 2" \
-H "Content-Type: application/json" \
-d '{"content": "This is the second message"}' \
'http://localhost:8085/conversations/1/messages'
在 Jaeger 用户界面中再检查通知器 span。您会看到父 span 和两个子 span,每个 span 都有“发送通知”操作:
现在您可以完全实现第一个和第二个目标,因为您可以看到一个请求在新的消息流中所经历的所有步骤。在每个 span 上所花费的时间能够暴露这些步骤之间的任何延迟。
如欲全面了解消息流,您还需要确认以下一点。通知器服务处理的事件实际上是信使服务派发的事件吗?
您不必做出任何明确的修改便可连接这两个追踪,但您也不能盲目相信自动埋点。
考虑到这一点,您可以添加一些快速调试代码,以验证在 NGINX 服务中启动的追踪确实与通知器服务所用的追踪相同(具有相同的追踪 ID)。
打开信使代码库的 app 目录下的 index.mjs 文件,并进行以下修改:
在顶部的导入语句列表的末尾添加以下代码:
import { trace } from "@opentelemetry/api";
在现有的黑色行下面添加橙色高亮显示的代码:
async function createMessageInConversation(req, res) {
const tracer = trace.getActiveSpan();
console.log("TRACE_ID: ", tracer.spanContext().traceId);
新行列出了信使中处理新消息创建的函数内的 TRACE_ID
。
打开通知器代码库的 app 目录下的 index.mjs 文件,并在现有的黑色行下面添加橙色高亮显示的行:
export async function handleMessageConsume(channel, msg, handlers) { console.log("RABBIT_MQ_MESSAGE: ", msg);
新行列出了通知服务接收的 AMQP 事件的全部内容。
通过在信使和通知器这两个终端运行这些命令,停止并重启信使和通知器服务:
^cnode --import ./tracing.mjs index.mjs
等待大约 10 秒钟,然后在客户端终端上再次发送信息:
curl -X POST \ -H "User-Id: 2" \
-H "Content-Type: application/json" \
-d '{"content": "This is the second message"}' \
'http://localhost:8085/conversations/1/messages'
查看信使服务和通知器服务的日志。在信使服务日志中,如下所示的一行报告了消息的链路追踪 ID(当您自己操作本教程步骤时,实际 ID 会有所不同):
TRACE_ID: 29377a9b546c50be629c8e64409bbfb5
同样地,通知器服务日志在如下所示的输出中报告链路追踪 ID:
_spanContext: {
traceId: '29377a9b546c50be629c8e64409bbfb5',
spanId: 'a94e9462a39e6dbf',
traceFlags: 1,
traceState: undefined
},
在控制台中链路追踪 ID 是匹配的,但最后您可以将其与 Jaeger 用户界面中的链路追踪 ID 进行比较。在相关链路追踪 ID 端点(您的端点会有所不同,但在本例中是 http://localhost:16686/trace/29377a9b546c50be629c8e64409bbfb5),打开用户界面,查看整个链路追踪。Jaeger 链路追踪确认:
注:在实际生产系统中,一旦确认流程按预期运行,即可删除您在本部分中添加的代码。
整个教程下来,您创建了不少容器和镜像!使用以下指令来将其删除。
删除任何正在运行的 Docker 容器:
docker rm $(docker stop messenger-lb)
删除平台服务以及信使与通知器数据库服务:
cd ~/microservices-march/platform && docker compose down
cd ~/microservices-march/notifier && docker compose down
cd ~/microservices-march/messenger && docker compose down
恭喜,您已学完本教程!
然而,您接触的仅仅是链路追踪配置的基础知识!在生产环境中,您可能希望为每个数据库查询添加自定义 span 等内容,并在所有 span 上添加额外的元数据来描述运行时细节(例如每个服务的容器 ID)。您还可以实现另外两种 OTel 数据类型(指标和日志),以全面了解系统的运行状况。
如欲进一步了解有关微服务的更多内容,请参与免费线上教学项目 Microservices June 微服务之月 2023。在第四单元:借助可观测性管理混沌而复杂的微服务中,您将了解三种主要的可观测性数据类型、基础设施和应用匹配的重要性,以及开始分析深度数据的方法。
"This blog post may reference products that are no longer available and/or no longer supported. For the most current information about available F5 NGINX products and solutions, explore our NGINX product family. NGINX is now part of F5. All previous NGINX.com links will redirect to similar NGINX content on F5.com."