本文转载自微信公众号《黑客下午茶》,作者少。转载本文请联系黑客下午茶公众号。目录什么是跟踪?为什么要追踪?跟踪、事务和跨度示例:调查缓慢的页面加载更多示例跟踪数据模型更多信息TraceDistributed中的数据采样一致性跟踪跟踪)通过捕获软件系统之间的交互提供相关错误和事务的关联视图。通过跟踪,Sentry可以跟踪您的软件性能并显示错误对多个系统的影响。将您的前端连接到您的后端。https://docs.sentry.io/product/sentry-basics/tracing/distributed-tracing/启用性能监控以增加您现有的错误数据,跟踪从前端到后端的交互。通过跟踪,Sentry可以跟踪您的软件性能,测量吞吐量和延迟等指标,并显示错误对多个系统的影响。跟踪使Sentry成为更完整的监控解决方案,帮助您更快地诊断问题并衡量应用程序的整体性能健康状况.Sentry中的跟踪提供了以下见解:特定错误事件或问题发生了什么导致应用程序出现瓶颈或延迟问题最耗时的端点或操作的条件什么是跟踪?首先,请注意跟踪不是什么:跟踪不是分析。尽管分析和跟踪的目标有很多重叠,而且它们都可以用于诊断应用程序中的问题,但它们在测量内容和数据记录方式方面有所不同。分析器可以测量应用程序操作的多个方面:执行的指令数、各种进程使用的内存量、给定函数调用所花费的时间等。生成的分析结果是这些测量结果的统计摘要。https://en.wikipedia.org/wiki/Profiling_(computer_programming)另一方面,跟踪工具关注的是发生了什么(以及何时),而不是发生了多少次或花费了多长时间。结果跟踪是程序执行期间发生的事件的日志,通常跨多个系统。虽然最常见的跟踪-或者,就Sentry的跟踪而言,总是-包括时间戳,允许计算持续时间,但测量性能并不是它们的唯一目的。它们还可以显示互连系统如何相互作用以及一个系统中的问题如何导致另一个系统出现问题。https://en.wikipedia.org/wiki/Tracing_(software)为什么要追踪?应用程序通常由互连的组件组成,也称为服务。作为示例,让我们看一下由以下组件组成的现代Web应用程序,这些组件由网络边界分隔:前端(单页应用程序)前端后端(RESTAPI)后端任务队列数据库服务器数据库服务器Cron作业调度程序这些组件中的每一个cron任务调度程序可以在不同的平台上用不同的语言编写。每个都可以使用SentrySDK单独检测以捕获错误数据或崩溃报告,但这种检测不提供完整的图片,因为每个部分都是单独考虑的。跟踪允许您将所有数据联系在一起。在我们的示例Web应用程序中,跟踪意味着能够跟踪从前端到后端和后端的请求,从请求创建的任何后台任务或通知作业中提取数据。这不仅允许您关联Sentry错误报告以查看一个服务中的错误如何传播到另一个服务,而且还可以让您更深入地了解哪些服务可能对应用程序的整体性能产生负面影响。在学习如何在您的应用程序中启用跟踪之前,了解一些关键术语以及它们之间的关系会有所帮助。Traces、Transactions和Spanstraces表示您要测量或跟踪的整个操作的记录-例如页面加载、用户在您的应用程序中完成某些操作的实例或后端的cron作业。当一个跟踪包括在多个服务中工作时,例如上面列出的那些,它被称为分布式跟踪,因为跟踪分布在这些服务之间。每条轨迹由一个或多个称为事务的树结构组成,其节点称为跨度。在大多数情况下,每个事务代表被调用服务的单个实例,并且该事务中的每个跨度代表该服务执行单个工作单元,无论是调用该服务中的函数还是调用不同的服务。这是一个示例跟踪,分解为事务和跨度:由于事务具有树结构,因此顶级跨度本身可以分解为更小的跨度,这反映了一个函数可能调用许多其他更小函数的方式;这是使用父子比喻来表达的,因此每个跨度都可以是多个其他子跨度的父跨度。此外,由于所有树都必须有根,因此每个事务中的一个跨度始终表示事务本身,并且事务中的所有其他跨度都从该根跨度派生。这是上图中其中一个事务的放大视图:为了使所有这些更加具体,让我们再次考虑我们的示例Web应用程序。示例:调查缓慢的页面加载假设您的Web应用程序加载缓慢并且您想知道原因。首先要使您的应用程序可用,必须做很多事情:对后端的多个请求,可能是一些工作——包括对数据库或外部API的调用——在返回响应并由浏览器处理以呈现之前完成所有转换将数据返回到对用户有意义的东西中。那么这个过程的哪一部分正在减慢速度?假设在这个简化的示例中,当用户在浏览器中加载应用程序时,每个服务中会发生以下情况:Browser(浏览器)HTML、CSS和JavaScript各1个请求1个渲染任务,触发2个JSON数据请求^Backend(后端)3个服务于静态文件(HTML、CSS和JS)的请求2个JSON数据请求-1个需要调用数据库-1个需要调用外部API并在将结果返回到前端之前处理结果^数据库服务器1个查询以检查身份验证1个查询以获取data1requestrequires2queries注意:外部API未完全列出,因为它是外部的,因此您看不到它的内部。在此示例中,整个页面加载过程(包括上述所有过程)由一条轨迹表示。跟踪将包含以下事务:1个浏览器事务(用于页面加载)5个后端事务(一个用于每个请求)1个数据库服务器事务(用于单个数据库请求)每个事务将被分解跨度如下:浏览器页面加载事务:7个span,2个sub-spans,每个JSON请求一个rootspan,整个页面每个HTML、CSS、JS请求一个(共3个)渲染任务1个span,它本身包含这里暂停一下强调一点:此处列出的一些(尽管不是全部)浏览器事务与前面列出的后端事务有直接对应关系。具体来说,浏览器事务中的每个请求跨度对应于后端中的一个单独的请求事务。在这种情况下,当一个服务中的跨度导致后续服务中的事务时,我们将原始跨度称为事务的父跨度及其根跨度。在下图中,波浪线表示这种父子关系。在我们的示例中,除了初始浏览器页面加载事务之外的每个事务都是另一个服务中跨度的子级,这意味着除了浏览器事务根之外的每个根跨度都有一个父跨度(尽管在不同的服务中)。在一个完全检测的系统(一个为每个服务启用跟踪的系统)中,这种模式将始终适用。唯一的无父跨度将是初始事务的根;每隔一个span都会有一个父级。此外,父母和孩子将始终生活在同一个服务中,除非子跨度是子事务的根,在这种情况下,父跨度将在调用服务中,子事务/子根跨度将在被调用的服务中。换句话说,一个完全检测的系统创建一个跟踪,它本身就是一个连接的树——每个事务都是一个子树——在这棵树中,子树/事务之间的边界正是服务之间的边界。上图显示了我们示例的完整跟踪树的一个分支。现在,为了完整起见,回到我们的跨度:后端HTML/CSS/JS请求事务:1个根跨度(浏览器跨度的子级)代表整个请求1个跨度,带有数据库调用事务后端请求:2个跨度1个根跨度代表整个请求(浏览器跨度的子级)1个用于查询数据库的跨度(数据库服务器事务的父级)^具有API调用事务的后端请求:3个跨度1个跨度代表整个请求的根跨度(浏览器跨度的子级))1个span用于API请求(不是父span,不像数据库调用,因为API是外部的)1个span用于处理API数据^数据库服务器请求事务:3个span1代表整个请求的根span(后端的子项)spanabove)1span用于认证查询1span用于查询和检索数据rver导致速度下降,花费了完成整个页面加载过程所需时间的一半以上。跟踪无法告诉您为什么会发生这种情况,但至少现在您知道去哪里找了!更多示例本节包含更多跟踪示例,分解为事务和跨度。测量特定的用户操作如果您的应用程序涉及电子商务,您可能希望测量用户点击“提交订单”与订单确认出现之间的时间,包括跟踪向支付处理器提交费用和发送订单确认电子邮件。整个过程是一个跟踪,通常你会有事务(T)和跨度(S)用于:浏览器完整过程(T和根跨度S)XHR请求到后端*(S)为你呈现确认屏幕(S)^后端对请求的处理(T和根跨度S)函数调用(S)计算总数存储订单数据库(DB)调用*(S)API调用支付处理器(S)电子邮件确认队列*(S)^数据库更新客户订单历史的作业(T和根跨度S)单个SQL查询(S)^发送电子邮件的队列任务(T和根跨度S)填充电子邮件模板的函数调用API调用到电子邮件发送服务注意:带星号的跨度表示父跨度是后续事务(及其根跨度)。监控后台进程如果你的后端定期轮询外部服务的数据,处理它,缓存它,然后将它转发给内部服务,这种情况发生的每个实例都是一个痕迹,你通常有以下事务(T)和跨度(S):完成整个过程的cron作业(T和rootspanS)调用外部服务的API(S)处理函数(S)调用缓存服务*(S)调用内部服务的API*(S)^完成的工作在您的缓存服务中(T和根跨度S)检查缓存中的现有数据(S)将新数据存储在缓存中(S)^内部服务对请求的处理(T和根跨度S)服务可能执行的任何操作处理请求注意:带星号的跨度表示父跨度是后续事务(及其根跨度)。跟踪数据模型“给我看你的流程图并隐藏你的表格,我仍然感到困惑。如果你给我看你的表格,那么我就不再需要你的流程图了,因为它们太明显了。"FredBrooks,《The Mythical Man-Month》(人月神话)虽然这个理论很有趣,但归根结底,任何数据结构都是由它包含的数据类型定义的,而数据结构之间的关系是由它们之间的链接如何记录来定义的。Traces、transactions、span也不例外,Traces(踪迹)Traces本身并不是一个实体,traces被定义为共享一个trace_id值的所有transaction的集合,Transactions(事务)Transaction与其共享大部分属性根跨度(开始和结束时间、标签等),因此下面描述的跨度的相同选项在事务中可用,并且在任一位置设置它们是等效的。事务还有一个不包含在跨度中的附加属性,称为transaction_name,用于在UI中标识一个交易。transaction_name取值的常见例子包括后端请求交易的端点路径(如/store/checkout/或api/v2/users//),task用于cron作业事务的名称(例如data.cleanup.delete_inactive_users)和页面加载事务的URL(如https://docs.sentry.io/performance-monitoring/distributed-tracing/)。Spans(跨度)事务中的大部分数据驻留在单个跨度中。span数据包括:parent_span_id:将span与其父op相关联op:一个短字符串,用于标识span正在测量的操作类型或类别start_timestamp:当span打开时end_timestamp:当span关闭时description:对span的更长描述操作,唯一标识跨度,但在跨度实例之间保持一致(可选)状态:指示操作状态的短代码(可选)标签:包含有关跨度的附加数据的键值对(可选)数据:有关一个的附加数据span的任意结构(可选)一起使用的op和description属性示例是op:sql.query和description:SELECT*FROMusersWHERElast_active<%s.status属性通常用于指示span操作的成功或失败,或者在HTTP请求的情况下的响应代码。最后,标签和数据允许您将更多上下文信息附加到span,例如function:middleware.auth.is_authenticated用于函数调用或request:{url:...,headers:...,body:...}Used对于HTTP请求。关于跟踪、事务和跨度以及它们如何相互关联的一些更重要的要点:跟踪持续时间由于跟踪只是事务的集合,因此跟踪没有自己的开始和结束时间。相反,跟踪在其最早的事务开始时开始,并在其最新的事务结束时结束。因此,您不能直接明确地开始或结束跟踪。相反,您通过在该跟踪中创建第一个事务来创建跟踪,并通过完成它包含的所有事务来完成跟踪。异步事务由于异步处理的可能性,子事务的生命周期可能比包含其父跨度的事务长许多数量级。例如,如果后端API调用启动了一个长时间运行的处理任务,然后立即返回响应,则后端事务将在异步任务事务完成之前很久完成(并且其数据将发送到Sentry)。异步性还意味着事务发送(和接收)到Sentry的顺序与它们的创建顺序没有任何关系。(相比之下,同一轨迹中交易的接收顺序与完成顺序相关,但由于传输时间的可变性等因素,相关性远非完美。)孤儿交易(orphantransactions)理论上,在一个fullyinstrumentedsystem,每个跟踪应该只包含一个事务和一个跨度(事务的根),没有父项,即原始服务中的事务。但是,在实践中,您可能不会在每个服务中都启用跟踪,或者由于网络中断或其他不可预见的情况,检测服务可能无法报告交易。发生这种情况时,您可能会在跟踪层次结构中看到间隙。具体来说,您可能会在某个跨度中途看到一个事务,该跨度的父跨度尚未被记录为任何已知事务的一部分。这种非发起的、无父母的交易被称为孤儿交易。嵌套跨度尽管我们上面的示例在其层次结构中有四个级别(跟踪、事务、跨度、子跨度),但是跨度的嵌套深度没有设置限制。但是,存在实际限制:发送到Sentry的事务有效负载具有最大允许大小,并且与任何类型的日志记录一样,需要在数据粒度和可用性之间取得平衡。Zero-durationSpans(零时长跨度)跨度可能具有相同的开始时间和结束时间,因此被记录为不占用时间。这可能是因为跨度被用作标记(例如在浏览器的性能API中完成),或者因为操作花费的时间少于测量分辨率(这将因服务而异)。https://developer.mozilla.org/en-US/docs/Web/API/Performance/markClockSkew(时钟偏差)如果从多台机器上采集交易,可能会遇到时钟偏差,其中一个交易的时间戳是与另一笔交易中的时间戳不一致。例如,如果您的后端进行数据库调用,则后端事务逻辑上应该在数据库事务之前开始。但是,如果每台机器(分别托管后端和数据库的机器)上的系统时间未同步到一个公分母,则情况可能并非如此。也有可能顺序是正确的,但两条记录的时间范围没有以准确反映实际发生情况的方式排列。为减少这种可能性,我们建议使用网络时间协议(NTP)或您的云提供商的时钟同步服务。数据是如何发送的个人跨度不会发送到哨兵;相反,整个交易作为一个单元发送。这意味着在它们所属的事务关闭并派发之前,Sentry的服务器不会记录任何跨度数据。然而,反之则不然——虽然跨度不能在没有事务的情况下发送,但事务仍然有效并且将被发送,即使它们包含的唯一跨度是它们的根跨度。数据采样当您在跟踪设置中启用采样时,您可以选择将收集到的事务发送到Sentry的百分比。例如,如果您有一个端点每分钟接收1000个请求,则0.25的采样率将导致每分钟大约250个事务(25%)被发送到Sentry。(这个数字是近似值,因为每个请求要么被跟踪,要么被独立和伪随机地以25%的概率进行跟踪。所以以同样的方式,100个公平硬币,当翻转时,产生大约50个正面,SDK将“决定”收集跟踪在大约250个案例中。)由于您知道抽样百分比,因此可以推断出总流量。收集痕迹时,出于两个原因,我们建议您对数据进行采样。首先,虽然捕获单个跟踪的开销很小,但在每个页面加载或每个API请求上捕获跟踪可能会给您的系统增加不需要的负载量。其次,启用采样可以让您更好地管理发送到Sentry的事件数量,以便您可以根据组织的需要对其进行定制。选择采样率时,目标不是收集太多数据(出于上述原因),而是收集足够的数据以便得出有意义的结论。如果您不确定选择什么速率,我们建议您从较低的值开始,然后随着您对流量模式和流量的了解逐渐增加,直到您找到一个可以让您平衡性能和流量与数据准确性之间关系的速率。追踪中的一致性对于涉及多个事务的追踪,Sentry使用“head-based”方法:在原始服务中做出采样决策,然后传递给所有后续服务。要了解这是如何工作的,让我们回到上面的webapp示例。假设有两个用户A和B,他们都在各自的浏览器中加载了一个应用程序。当A加载应用程序时,SDK伪随机地“决定”收集踪迹,而当B加载应用程序时,SDK“决定”不收集踪迹。当每个浏览器向您的后端发出请求时,它会在这些请求的标头中包含“是,请收集交易)”或“否,这次不收集交易”的决定。当您的后端处理来自浏览器A的请求时,它看到“是”的决定,收集事务和跨度数据,并将其发送给哨兵。此外,它在对后续服务(例如您的数据库服务器)发出的任何请求中包含一个“是”决定,这些服务同样会收集数据,将其发送到Sentry,并将决定传递给它们调用的任何服务。通过这个过程,A跟踪中的所有相关交易都被收集并发送给哨兵。另一方面,当你的后端处理来自B的浏览器的请求时,它看到一个“否”的决定,所以它不会收集并发送事务和跨度数据给哨兵。但是,在将决定传播到后续服务方面,它与A的情况相同,告诉它们也不要收集或发送数据。然后他们告诉他们调用的任何服务不要发送数据,这样就不会收集B跟踪的交易。简而言之:这种基于头部的方法的结果是在原始服务中做出一次决定,并传递给所有后续服务,以收集给定跟踪的所有事务,或者不收集,因此不应该有Any不完整的跟踪。
