Sentry 监控 - 面向全栈开发人员的分布式跟踪 101 系列教程(第一部分)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Sentry 监控 - 面向全栈开发人员的分布式跟踪 101 系列教程(第一部分)

欢迎来到我们关于全栈开发人员分布式跟踪(Distributed Tracing)的系列的第 1 部分。在本系列中,我们将学习分布式跟踪的细节,以及它如何帮助您监控全栈应用程序日益复杂的需求。


Web 的早期,编写 Web 应用程序很简单。开发人员使用 PHP 等语言在服务器上生成 HTML,与 MySQL 等单一关系数据库进行通信,大多数交互性由静态 HTML 表单组件驱动。虽然调试工具很原始,但理解代码的执行流程很简单。


在今天的现代 web 栈中,它什么都不是。全栈开发人员需要编写在浏览器中执行的 JavaScript,与多种数据库技术互操作,并在不同的服务器架构(例如:serverless)上部署服务器端代码。如果没有合适的工具,了解浏览器中的用户交互如何关联到服务器堆栈深处的 500 server error 几乎是不可能的。Enter:分布式跟踪。


微信图片_20220612235715.gif


我试图解释 2021 年我的 web 堆栈中的瓶颈。

分布式跟踪(Distributed tracing)是一种监控技术,它将多个服务之间发生的操作和请求联系起来。这允许开发人员在端到端请求从一个服务移动到另一个服务时“跟踪(trace)”它的路径,让他们能够查明对整个系统产生负面影响的单个服务中的错误或性能瓶颈。


在这篇文章中,我们将了解有关分布式跟踪概念的更多信息,在代码中查看端到端(end-to-end)跟踪示例,并了解如何使用跟踪元数据为您的日志记录和监控工具添加有价值的上下文。完成后,您不仅会了解分布式跟踪的基础知识,还会了解如何应用跟踪技术来更有效地调试全栈 Web 应用程序。


但首先,让我们回到开头:什么是分布式追踪?


分布式追踪基础



分布式跟踪是一种记录多个服务的连接操作的方法。通常,这些操作是由从一个服务到另一个服务的请求发起的,其中“请求(request)”可以是实际的 HTTP 请求,也可以是通过任务队列或其他一些异步方式调用的工作。


跟踪由两个基本组件组成:


  • Span 描述发生在服务上的操作或 “work”Span 可以描述广泛的操作——例如,响应 HTTP 请求的 web 服务器的操作——也可以描述单个函数的调用。
  • trace 描述了一个或多个连接 span 的端到端(end-to-end)旅程。如果 trace 连接在多个服务上执行的 span“work”),则该 trace 被认为是分布式跟踪。

让我们看一个假设的分布式跟踪示例。


微信图片_20220612235745.png


上图说明了 trace 如何从一个服务(一个在浏览器上运行的 React 应用程序)开始,并通过调用 API Web Server 继续,甚至进一步调用后台任务 worker。此图中的 span 是在每个服务中执行的 work,每个 span 都可以“追溯到(traced)”由浏览器应用程序启动的初始工作(initial work)。最后,由于这些操作发生在不同的服务上,因此该跟踪被认为是分布式的。


描述广泛操作的跨度(例如:响应 HTTP requestWeb server 的完整生命周期)有时被称为事务跨度(transaction spans),甚至只是事务。我们将在本系列的第 2 部分中更多地讨论事务与跨度(transactions vs. spans)。


跟踪和跨度标识符



到目前为止,我们已经确定了跟踪的组件,但我们还没有描述这些组件是如何链接在一起的。


首先,每个跟踪都用跟踪标识符(trace identifier)唯一标识。这是通过在根跨度(root span)中创建一个唯一的随机生成值(即 UUID)来完成的——这是启动整个跟踪的初始操作。在我们上面的示例中,根跨度出现在浏览器应用程序中。

其次,每个 span 首先需要被唯一标识。这通过在跨度开始其操作时创建唯一的跨度标识符(或 span_id)来完成。这个 span_id 创建应该发生在 trace 内发生的每个 span(或操作)处进行。


微信图片_20220612235805.png

image.gif

让我们重新审视我们假设的跟踪示例。在上图中,您会注意到跟踪标识符唯一地标识了跟踪,并且该跟踪中的每个跨度也拥有一个唯一的跨度标识符。


然而,生成 trace_idspan_id 是不够的。要实际连接这些服务,您的应用程序必须在从一个服务向另一个服务发出请求时传播所谓的跟踪上下文(trace context)。


跟踪上下文



跟踪上下文(trace context)通常仅由两个值组成:


  • 跟踪标识符(或 trace_id):在根跨度中生成的唯一标识符,用于标识整个跟踪。这与我们在上一节中介绍的跟踪标识符相同;它以不变的方式传播到每个下游服务。
  • 父标识符(或 parent_id):产生当前操作的“父”跨度的 span_id

下图显示了在一个服务中启动的请求如何将跟踪上下文传播到下游的下一个服务。您会注意到 trace_id 保持不变,而 parent_id 在请求之间发生变化,指向启动最新操作的父跨度。


微信图片_20220612235825.png


有了这两个值,对于任何给定的操作,就可以确定原始(root)服务,并按照导致当前操作的顺序重建所有父/祖先(parent/ancestor)服务。


工作示例(代码演示)



示例源码:

为了更好地理解这一点,让我们实际实现一个基本的跟踪实现,其中浏览器应用程序是由跟踪上下文连接的一系列分布式操作的发起者。


首先,浏览器应用程序呈现一个表单:就本示例而言,是一个“邀请用户(invite user)”表单。表单有一个提交事件处理程序,它在表单提交时触发。让我们将此提交处理程序视为我们的根跨度(root span),这意味着当调用处理程序时,会生成 trace_idspan_id


接下来,完成一些工作以从表单中收集用户输入的值,然后最后向我们的 Web 服务器发出一个到 /inviteUser API 端点的 fetch 请求。作为此 fetch 请求的一部分,跟踪上下文作为两个自定义 HTTP header 传递:trace-idparent-id(即当前 spanspan_id)。


// browser app (JavaScript)
import uuid from 'uuid';
const traceId = uuid.v4();
const spanId = uuid.v4();
console.log('Initiate inviteUser POST request', `traceId: ${traceId}`);
fetch('/api/v1/inviteUser?email=' + encodeURIComponent(email), {
   method: 'POST',
   headers: {
       'trace-id': traceId,
       'parent-id': spanId,
   }
}).then((data) => {
   console.log('Success!');
}).catch((err) => {
   console.log('Something bad happened', `traceId: ${traceId}`);
});


请注意,这些是用于说明目的的非标准 HTTP header。作为 W3C traceparent 规范的一部分,正在积极努力标准化 tracing HTTP header,该规范仍处于 “Recommendation” 阶段。



在接收端,API web server 处理请求并从 HTTP 请求中提取跟踪元数据(tracing metadata)。然后它会排队一个 job 以向用户发送电子邮件,并将跟踪上下文作为 job 描述中“meta”字段的一部分附加。最后,它返回一个带有 200 状态 code 的响应,表明该方法成功。


请注意,虽然服务器返回了成功的响应,但实际的“工作”直到后台任务 worker 拿起新排队的 job 并实际发送电子邮件后才完成。


在某个点上,队列处理器开始处理排队的电子邮件作业。再一次,跟踪(trace)和父标识符(parent identifier)被提取出来,就像它们在 web server 中的早些时候一样。


// API Web Server
const Queue = require('bull');
const emailQueue = new Queue('email');
const uuid = require('uuid');
app.post("/api/v1/inviteUser", (req, res) => {
  const spanId = uuid.v4(),
    traceId = req.headers["trace-id"],
    parentId = req.headers["parent-id"];
  console.log(
    "Adding job to email queue",
    `[traceId: ${traceId},`,
    `parentId: ${parentId},`,
    `spanId: ${spanId}]`
  );
  emailQueue.add({
    title: "Welcome to our product",
    to: req.params.email,
    meta: {
      traceId: traceId,
      // the downstream span's parent_id is this span's span_id
      parentId: spanId,
    },
  });
  res.status(200).send("ok");
});
// Background Task Worker
emailQueue.process((job, done) => {
  const spanId = uuid.v4();
  const { traceId, parentId } = job.data.meta;
  console.log(
    "Sending email",
    `[traceId: ${traceId},`,
    `parentId: ${parentId},`,
    `spanId: ${spanId}]`
  );
  // actually send the email
  // ...
  done();
});


分布式系统 Logging



您会注意到,在我们示例的每个阶段,都会使用 console.log 进行 logging 调用,该调用还发出当前 tracespanparent 标识符。在完美的同步世界中——每个服务都可以登录到同一个集中式 logging 工具——这些日志语句中的每一个都会依次出现:


微信图片_20220612235859.png


如果在这些操作过程中发生异常或错误行为,使用这些或额外的日志语句来查明来源将相对简单。但不幸的现实是,这些都是分布式服务,这意味着:


  • Web 服务器通常处理许多并发请求。Web 服务器可能正在执行归因于其他请求的工作(并发出日志记录语句)。
  • 网络延迟会影响操作顺序。从上游服务发出的请求可能不会按照它们被触发的顺序到达目的地。
  • 后台 worker 可能有排队的 job。在到达此跟踪中排队的确切 job 之前,worker 可能必须先完成先前排队的 job


在一个更现实的例子中,我们的日志调用可能看起来像这样,它反映了同时发生的多个操作:


微信图片_20220612235921.png


如果不跟踪 metadata,就不可能了解哪个动作调用哪个动作的拓扑结构。但是通过在每次 logging 调用时发出跟踪 meta 信息,可以通过过滤 traceId 快速过滤跟踪中的所有 logging 调用,并通过检查 spanIdparentId 关系重建确切的顺序。

这就是分布式跟踪的威力:通过附加描述当前操作(span id)、产生它的父操作(parent id)和跟踪标识符(trace id)的元数据,我们可以增加日志记录和遥测数据以更好地理解 分布式服务中发生的事件的确切顺序。


在真实的分布式跟踪环境中



在本文的过程中,我们一直在使用一个有点人为的示例。在真正的分布式跟踪环境中,您不会手动生成和传递所有的跨度和跟踪标识符。您也不会依赖 console.log(或其他日志记录)调用来自己发出跟踪元数据。您将使用适当的跟踪库来为您处理检测和发送跟踪数据。


OpenTelemetry



OpenTelemetry 是一组开源工具、APISDK,用于检测、生成和导出正在运行的软件中的遥测数据。它为大多数流行的编程语言提供了特定于语言的实现,包括浏览器 JavaScriptNode.js



Sentry


Sentry 以多种方式使用这种遥测。例如,Sentry 的性能监控功能集使用跟踪数据生成瀑布图,说明跟踪中分布式服务操作的端到端延迟。


微信图片_20220612235946.png


Sentry 还使用跟踪元数据来增强它的错误监控功能,以了解在一个服务(如服务器后端)中触发的错误如何传播到另一个服务(如前端)中的错误。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
14天前
|
数据采集 存储 监控
公司监控软件:基于 PHP 的分布式监控系统设计
本文介绍了基于 PHP 的分布式监控系统的设计与实现。该系统包括监控节点、数据采集模块、数据传输模块和监控中心,能够高效地收集、传输和分析各节点的数据,确保系统的稳定运行和安全防护。通过示例代码展示了数据采集、传输及存储的具体实现方法,并强调了安全与可靠性的重要性。
36 3
|
23天前
|
JSON 分布式计算 前端开发
前端的全栈之路Meteor篇(七):轻量的NoSql分布式数据协议同步协议DDP深度剖析
本文深入探讨了DDP(Distributed Data Protocol)协议,这是一种在Meteor框架中广泛使用的发布/订阅协议,支持实时数据同步。文章详细介绍了DDP的主要特点、消息类型、协议流程及其在Meteor中的应用,包括实时数据同步、用户界面响应、分布式计算、多客户端协作和离线支持等。通过学习DDP,开发者可以构建响应迅速、适应性强的现代Web应用。
|
30天前
|
SQL NoSQL MongoDB
一款基于分布式文件存储的数据库MongoDB的介绍及基本使用教程
一款基于分布式文件存储的数据库MongoDB的介绍及基本使用教程
41 0
|
6月前
|
监控 Ubuntu Docker
Sentry 监控 Docker 方式部署
Sentry 监控 Docker 方式部署
465 0
|
6月前
|
监控 前端开发 JavaScript
Sentry 监控部署与使用(详细流程)
Sentry 监控部署与使用(详细流程)
854 0
|
3月前
|
SQL 监控 分布式数据库
【解锁数据库监控的神秘力量!】OceanBase社区版与Zabbix的完美邂逅 —— 揭秘分布式数据库监控的终极奥秘!
【8月更文挑战第7天】随着OceanBase社区版的普及,企业广泛采用这一高性能、高可用的分布式数据库。为保障系统稳定,使用成熟的Zabbix监控工具对其进行全方位监控至关重要。本文通过实例介绍如何在Zabbix中配置监控OceanBase的方法,包括创建监控模板、添加监控项(如TPS)、设置触发器及图形展示,并提供示例脚本帮助快速上手。通过这些步骤,可以有效监控OceanBase状态,确保业务连续性。
102 0
|
6月前
|
存储 监控 分布式数据库
Scala代码在局域网监控软件中的分布式处理
该文介绍了如何使用Scala进行局域网监控数据的分布式处理。通过示例展示了利用Scala的并发能力进行数据收集,使用集合操作进行数据处理与分析,以及如何将处理结果存储到分布式数据库(如Cassandra)和自动提交到网站。Scala的并发处理能力和丰富库支持使其在分布式处理中表现出色。
126 3
|
5月前
|
存储 运维 Prometheus
微服务监控:确保分布式系统的可观察性与稳定性
微服务监控:确保分布式系统的可观察性与稳定性
|
6月前
|
存储 JSON 监控
Erlang用于构建分布式屏幕监控软件的优点
Erlang是一种适用于并发编程的语言,特别适合构建分布式屏幕监控软件。其轻量级进程支持高并发,能同时处理多个屏幕的实时更新。Erlang的容错性和高可用性通过监督树机制保证了进程故障时的自动重启。此外,其内置的分布式特性使得跨节点的屏幕监控变得简单。Erlang还允许通过HTTP客户端库自动将监控数据提交到网站,便于数据存储和分析。因此,Erlang是构建此类软件的理想选择。
149 7
|
6月前
|
Prometheus 监控 Cloud Native
Golang深入浅出之-Go语言中的分布式追踪与监控系统集成
【5月更文挑战第4天】本文探讨了Go语言中分布式追踪与监控的重要性,包括追踪的三个核心组件和监控系统集成。常见问题有追踪数据丢失、性能开销和监控指标不当。解决策略涉及使用OpenTracing或OpenTelemetry协议、采样策略以及聚焦关键指标。文中提供了OpenTelemetry和Prometheus的Go代码示例,强调全面可观测性对微服务架构的意义,并提示选择合适工具和策略以确保系统稳定高效。
219 5

热门文章

最新文章