当前位置: 首页 > 科技观察

SentryMonitoring-全栈开发人员的分布式跟踪

时间:2023-03-17 12:08:54 科技观察

欢迎来到我们的全栈开发人员分布式跟踪系列的第1部分。在本系列中,我们将了解分布式跟踪的来龙去脉,以及它如何帮助您监控全栈应用程序日益复杂的需求。在web的早期,编写web应用程序很简单。开发人员使用PHP等语言在服务器上生成HTML,与MySQL等单一关系型数据库进行通信,大部分交互由静态HTML表单组件驱动。虽然调试工具很原始,但理解代码执行流程很简单。在当今的现代网络堆栈中,它什么都不是。全栈开发人员需要编写在浏览器中执行的JavaScript,与多种数据库技术进行互操作,并在不同的服务器架构(例如:无服务器)上部署服务器端代码。如果没有正确的工具,了解浏览器中的用户交互如何与服务器堆栈深处的500服务器错误相关联几乎是不可能的。输入:分布式跟踪。我正在尝试解释2021年我的Web堆栈中的瓶颈。分布式跟踪是一种监控技术,可以关联跨多个服务发生的操作和请求。这允许开发人员在请求从一个服务移动到另一个服务时“跟踪”端到端请求的路径,从而允许他们查明对整个系统产生负面影响的单个服务中的错误或性能瓶颈。在本文中,我们将详细了解分布式跟踪的概念,查看代码中端到端跟踪的示例,并了解如何使用跟踪元数据为您的日志记录和监控工具添加有价值的上下文。完成后,您不仅会了解分布式跟踪的基础知识,还会了解如何应用跟踪技术更有效地调试全栈Web应用程序。但首先,让我们回到开头:什么是分布式跟踪?分布式跟踪基础知识分布式跟踪是一种记录连接的多个服务的操作的方法。通常,这些操作是由一个服务向另一个服务发出的请求发起的,其中“请求”可以是实际的HTTP请求,也可以是通过任务队列或其他一些异步方式调用的工作。跟踪由两个基本组件组成:Span描述服务上发生的操作或“工作”。跨度可以描述广泛的操作——例如,Web服务器响应HTTP请求的操作——或者它们可以描述单个函数调用。跟踪描述了一个或多个连接跨度的端到端旅程。如果跟踪连接在多个服务上执行的span("work"),则该跟踪被视为分布式跟踪。让我们看一个假设的分布式跟踪示例。上图说明了跟踪如何从服务(在浏览器上运行的React应用程序)开始,并通过调用APIWeb服务器甚至进一步调用后台任务工作者继续。这张图中的span是每个服务中执行的工作,每个span都可以“回溯”到浏览器应用程序发起的初始工作。最后,由于这些操作发生在不同的服务上,因此该跟踪被认为是分布式的。描述广泛操作的跨度(例如:Web服务器响应HTTP请求的完整生命周期)有时称为事务跨度,甚至简称为事务。我们将在本系列的第2部分中详细讨论事务与跨度。Trace和Span标识符到目前为止,我们已经确定了trace的组件,但我们还没有描述这些组件是如何链接在一起的。首先,每条迹线都用迹线标识符唯一标识。这是通过在根跨度中创建一个随机生成的唯一值(即UUID)来完成的——这是启动整个跟踪的初始操作。在我们上面的示例中,根跨度出现在浏览器应用程序中。其次,每个跨度首先需要被唯一标识。这是通过在跨度开始运行时创建一个唯一的跨度标识符(或span_id)来完成的。跟踪中出现的每个跨度(或操作)都应该创建此span_id。让我们重新审视我们假设的跟踪示例。在上图中,您会注意到跟踪标识符唯一标识一条跟踪,并且该跟踪中的每个跨度也有一个唯一的跨度标识符。但是,生成trace_id和span_id是不够的。要实际连接这些服务,您的应用程序必须在从一项服务向另一项服务发出请求时传播所谓的跟踪上下文。Tracecontext跟踪上下文(tracecontext)通常只包含两个值:Traceidentifier(或trace_id):在rootspan中生成的唯一标识符,用于标识整个trace。这与我们在上一节中介绍的跟踪标识符相同;它以不变的方式传播到每个下游服务。父标识符(或parent_id):产生当前操作的“父”跨度的span_id。下图显示了在一个服务中发起的请求如何将跟踪上下文传播到下游的下一个服务。您会注意到trace_id保持不变,而parent_id在请求之间发生变化,指向启动最近操作的父跨度。使用这两个值,对于任何给定的操作,都可以确定原始(根)服务并按照导致当前操作的顺序重建所有父/祖先服务。工作示例(代码演示)示例源代码:https://github.com/getsentry/distributed-tracing-examples为了更好地理解这一点,让我们实际实现一个基本的跟踪实现,其中浏览器应用程序由一系列分布式跟踪的发起者跟踪连接到上下文的操作。首先,浏览器应用程序呈现一个表单:在本例中为“邀请用户”表单。该表单有一个提交事件处理程序,该处理程序在提交表单时触发。让我们将这个提交处理程序视为我们的根跨度,这意味着当调用处理程序时,将生成一个trace_id和span_id。接下来,做一些工作来从表单中收集用户输入的值,然后最后向我们的网络服务器发出一个获取请求到/inviteUserAPI端点。作为此获取请求的一部分,跟踪上下文作为两个自定义HTTP标头传递:trace-id和parent-id(即当前跨度的span_id)。//browserapp(JavaScript)importuuidfrom'uuid';consttraceId=uuid.v4();constspanId=uuid.v4();console.log('InitiateinviteUserPOSTrequest',`traceId:${traceId}`);fetch('/api/v1/inviteUser?email='+encodeURIComponent(email),{method:'POST',headers:{'trace-id':traceId,'parent-id':spanId,}}).then((data)=>{console.log('成功!');}).catch((err)=>{console.log('Somethingbadhappened',`traceId:${traceId}`);});请注意,出于描述目的,这些是使用非标准HTTP标头编写的。作为W3Ctraceparent规范的一部分,正在积极努力将跟踪HTTP标头标准化,该规范仍处于“推荐”阶段。https://www.w3.org/TR/trace-context/在接收端,APIWeb服务器处理请求并从HTTP请求中提取跟踪元数据。然后,它将作业排队以通过电子邮件发送给用户,并将跟踪上下文附加为作业描述中“元”字段的一部分。最后,它返回一个带有200状态码的响应,表示方法成功。请注意,虽然服务器返回了成功的响应,但实际的“工作”并没有完成,直到后台任务工作人员拿起新排队的工作并实际发送电子邮件。在某个时候,队列处理器开始处理排队的电子邮件作业。同样,跟踪和父标识符被提取,就像它们在Web服务器中的早期一样。//APIWebServerconstQueue=require('bull');constemailQueue=newQueue('email');constuuid=require('uuid');app.post("/api/v1/inviteUser",(req,res)=>{constspanId=uuid.v4(),traceId=req.headers["trace-id"],parentId=req.headers["parent-id"];console.log("Addingjobtoemailqueue",`[traceId:${traceId},`,`parentId:${parentId},`,`spanId:${spanId}]`);emailQueue.add({title:"Welcometoourproduct",to:req.params.email,meta:{traceId:traceId,//thedownstreamspan'sparent_idisthisspan'sspan_idparentId:spanId,},});res.status(200).send("ok");});//BackgroundTaskWorkeremailQueue.process((job,done)=>{constspanId=uuid.v4();const{traceId,parentId}=job.data.meta;console.log("发送邮件",`[traceId:${traceId},`,`parentId:${parentId},`,`spanId:${spanId}]`);//实际发送邮件//...完成();});分布式系统日志记录您会注意到,在我们示例的每个阶段,都会使用console.log进行日志记录调用,它还会发出当前跟踪、跨度和父标识符。在一个完全同步的世界中——每个服务都可以记录到同一个集中式日志记录设施——这些日志语句中的每一个都会依次出现:如果在这些操作期间发生异常或不当行为,使用这些或额外的日志语句来查明来源将是比较简单。但不幸的是,这些是分布式服务,这意味着:Web服务器通常处理许多并发请求。Web服务器可能正在执行归因于其他请求的工作(并发出日志记录语句)。网络延迟会影响操作顺序。来自上游服务的请求可能不会按照它们被触发的顺序到达目的地。后台工作者可能有作业排队。在到达此跟踪中排队的确切作业之前,工作人员可能必须完成先前排队的作业。在一个更现实的例子中,我们的日志调用可能看起来像这样,反映了同时发生的多个动作:如果没有跟踪元数据,就不可能了解哪个动作调用哪个动作的拓扑结构。但是通过在每个日志记录调用上发出跟踪元信息,可以通过过滤traceId快速过滤跟踪中的所有日志记录调用,并通过检查spanId和parentId关系重建准确的顺序。这就是分布式跟踪的强大功能:通过附加描述当前操作的元数据(spanid)、产生它的父操作(parentid)和跟踪标识符(traceid),我们可以增加日志记录和遥测数据以更好地理解分布式服务中发生的事件的确切顺序。在真实的分布式跟踪环境中在本文的整个过程中,我们一直在使用一个有些人为设计的示例。在真正的分布式跟踪环境中,您不会手动生成和传递所有跨度和跟踪标识符。您也不依赖于console.log(或其他日志记录)调用来自己发出跟踪元数据。您将使用适当的跟踪库来为您处理检测和发送跟踪数据。OpenTelemetryOpenTelemetry是一组开源工具、API和SDK,用于从运行的软件中检测、生成和导出遥测数据。它为大多数流行的编程语言(包括浏览器JavaScript和Node.js)提供特定于语言的实现。https://opentelemetry.io/https://github.com/open-telemetry/opentelemetry-jsSentrySentry以多种方式使用此遥测。例如,Sentry的性能监控功能集使用跟踪数据生成瀑布图,说明跟踪中分布式服务操作的端到端延迟。Sentry还通过跟踪元数据增强了其错误监控功能,以了解在一个服务(例如服务器后端)中触发的错误如何传播到另一个服务(例如前端)中的错误。

最新推荐
猜你喜欢