分布式追踪
编辑分布式追踪编辑
一个 trace
是一个 事务 和 跨度 的集合,它们具有共同的根。每个 trace
跟踪单个请求的完整过程。当一个 trace
穿越多个服务时,就像微服务架构中常见的那样,它被称为分布式追踪。
为什么分布式追踪很重要?编辑
分布式追踪使您能够通过追踪请求的完整过程来分析整个微服务架构的性能,从前端服务的初始 Web 请求一直到后端服务执行的数据库查询。
跟踪请求在服务中的传播,可以提供应用程序在何处花费时间、错误发生的位置以及瓶颈形成位置的端到端视图。分布式追踪消除了各个服务的孤立数据,并揭示了服务边界之外发生的事情。
对于支持的技术,分布式追踪无需任何额外配置即可开箱即用。
分布式追踪的工作原理编辑
分布式追踪通过将自定义的 traceparent
HTTP 标头注入到传出的请求中来实现。此标头包含信息,例如 trace-id
,用于标识当前的追踪,以及 parent-id
,用于标识当前跨度的父级,在传入请求中是当前跨度的父级,在传出请求中是当前跨度本身。
当服务处理请求时,它会检查此 HTTP 标头是否存在。如果不存在,服务会启动一个新的追踪。如果存在,服务会确保将当前操作添加为现有追踪的子级,并继续传播追踪。
追踪传播示例编辑
在此示例中,Elastic 的 Ruby 代理与 Elastic 的 Java 代理通信。两者都支持 traceparent
标头,并且追踪数据成功传播。
在此示例中,Elastic 的 Ruby 代理与 OpenTelemetry 的 Java 代理通信。两者都支持 traceparent
标头,并且追踪数据成功传播。
在此示例中,追踪遇到了一个不传播 traceparent
标头的中间件。分布式追踪结束,任何进一步的通信都将导致新的追踪。
W3C 追踪上下文规范编辑
所有 Elastic 代理现在都支持官方的 W3C 追踪上下文规范和 traceparent
标头。请参阅下表以了解所需的最低代理版本。
代理名称 | 代理版本 |
---|---|
Go 代理 |
≥ |
Java 代理 |
≥ |
.NET 代理 |
≥ |
Node.js 代理 |
≥ |
PHP 代理 |
≥ |
Python 代理 |
≥ |
Ruby 代理 |
≥ |
RUM 代理 |
≥ |
旧版本的 Elastic 代理使用唯一的 elastic-apm-traceparent
标头。为了向后兼容,新版本的 Elastic 代理仍然支持此标头。
可视化分布式追踪编辑
APM 应用程序的时间线可视化提供了对应用程序每个追踪的视觉深入分析。
手动分布式追踪编辑
Elastic 代理会自动为支持的技术传播分布式追踪上下文。如果您的服务通过其他不支持的协议进行通信,您可以使用每个代理的 API 手动将分布式追踪上下文从发送服务传播到接收服务。
将 traceparent
标头添加到传出的请求编辑
发送服务必须将 traceparent
标头添加到传出的请求。
不适用。
- 使用
StartTransaction
启动事务,或使用StartSpan
启动跨度。 - 获取活动的
TraceContext
。 - 将
TraceContext
发送到接收服务。
示例
代理将自动使用 URLSessions
将追踪标头注入到网络请求中,但如果您使用非标准网络库,则可能需要手动注入它们。这将使用 OpenTelemetry API 完成。
- 创建一个
Setter
- 根据 Open Telemetry 标准 为每个跨度创建一个
Span
- 将追踪上下文注入到标头字典中
- 按照您的网络库的步骤完成网络请求。确保在请求成功或失败时调用
span.end()
。
import OpenTelemetryApi import OpenTelemetrySdk struct BasicSetter: Setter { func set(carrier: inout [String: String], key: String, value: String) { carrier[key] = value } } let span : Span = ... let setter = BasicSetter() let propagator = W3CTraceContextPropagator() var headers = [String:String]() propagator.inject(spanContext: span.context, carrier: &headers, setter:setter) let request = URLRequest(...) request.allHTTPHeaderFields = headers ... // make network request span.end()
- 使用
startTransaction
启动事务,或使用startSpan
启动跨度。 - 使用
injectTraceHeaders
将traceparent
标头注入到请求对象中。
手动检测 RPC 框架的示例
// Hook into a callback provided by the RPC framework that is called on outgoing requests public Response onOutgoingRequest(Request request) throws Exception { Span span = ElasticApm.currentSpan() .startSpan("external", "http", null) .setName(request.getMethod() + " " + request.getHost()); try (final Scope scope = transaction.activate()) { span.injectTraceHeaders((name, value) -> request.addHeader(name, value)); return request.execute(); } catch (Exception e) { span.captureException(e); throw e; } finally { span.end(); } }
- 使用
CurrentTransaction
或CurrentSpan
序列化活动事务或跨度的分布式追踪上下文。 - 将序列化的上下文发送到接收服务。
示例
string outgoingDistributedTracingData = (Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData ?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData)?.SerializeToString(); // Now send `outgoingDistributedTracingData` to the receiving service
- 使用
apm.startTransaction()
启动事务,或使用apm.startSpan()
启动跨度。 - 使用
currentTraceparent
获取已启动事务/跨度的序列化traceparent
字符串。 - 对
traceparent
进行编码,并在您的常规请求中将其发送到接收服务。
使用原始 UDP 在两个服务 A 和 B 之间进行通信的示例
- 在客户端(即发送请求的一方)获取当前的分布式追踪上下文。
- 将当前的分布式追踪上下文序列化为请求传输支持的格式,并将其发送到服务器端(即接收请求的一方)。
示例
- 使用
begin_transaction()
启动事务。 - 获取活动事务的
trace_parent
。 - 将
trace_parent
发送到接收服务。
示例
- 使用
with_span
启动跨度。 - 获取活动的
TraceContext
。 - 将
TraceContext
发送到接收服务。
解析传入请求中的 traceparent
标头编辑
接收服务必须解析传入的 traceparent
标头,并作为接收上下文的子级启动一个新的事务或跨度。
不适用。
- 使用
ParseTraceparentHeader
或ParseTracestateHeader
解析传入的TraceContext
。 - 使用
StartTransactionOptions
或StartSpanOptions
作为传入事务的子级启动一个新的事务或跨度。
示例
// Receive incoming TraceContext traceContext, _ := apmhttp.ParseTraceparentHeader(r.Header.Get("Traceparent")) traceContext.State, _ = apmhttp.ParseTracestateHeader(r.Header["Tracestate"]...) opts := apm.TransactionOptions{ TraceContext: traceContext, } transaction := apm.DefaultTracer().StartTransactionOptions("GET /", "request", opts)
不适用。
- 使用
startTransactionWithRemoteParent()
作为传入事务的子级创建一个事务。 - 使用
activate()
和setName()
启动并命名事务。
示例
// Hook into a callback provided by the framework that is called on incoming requests public Response onIncomingRequest(Request request) throws Exception { // creates a transaction representing the server-side handling of the request Transaction transaction = ElasticApm.startTransactionWithRemoteParent(request::getHeader, request::getHeaders); try (final Scope scope = transaction.activate()) { String name = "a useful name like ClassName#methodName where the request is handled"; transaction.setName(name); transaction.setType(Transaction.TYPE_REQUEST); return request.handle(); } catch (Exception e) { transaction.captureException(e); throw e; } finally { transaction.end(); } }
反序列化传入的分布式跟踪上下文,并将其传递给任何 StartTransaction
或 CaptureTransaction
API(所有这些 API 都有一个可选的 DistributedTracingData
参数)。这将创建一个新的事务或跨度,作为传入跟踪上下文的子级。
启动新事务的示例
var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction", DistributedTracingData.TryDeserializeFromString(serializedDistributedTracingData));
- 解码并存储接收服务中的
traceparent
。 - 将
traceparent
作为childOf
选项传递,以手动启动一个新的事务,作为使用apm.startTransaction()
接收到的traceparent
的子级。
通过原始 UDP 接收 traceparent
的示例
- 在服务器端接收分布式跟踪数据。
- 使用代理的公共 API 开始一个新的事务。例如,使用
ElasticApm::beginCurrentTransaction
并将接收到的分布式跟踪数据(序列化为字符串)作为参数传递。这将创建一个新的事务,作为传入跟踪上下文的子级。 - 不要忘记最终在服务器端结束事务。
示例
$receiverTransaction = ElasticApm::beginCurrentTransaction( 'GET /data-api', 'data-layer', /* timestamp */ null, $distDataAsString );
一旦在接收服务中创建了这个新的事务,您就可以创建子跨度,或者像往常一样使用任何其他代理 API 方法。
- 从字符串或 HTTP 标头创建
TraceParent
对象。 - 通过传入
TraceParent
对象,启动一个新的事务,作为TraceParent
的子级。
使用 HTTP 标头的示例
parent = elasticapm.trace_parent_from_headers(headers_dict) client.begin_transaction('processors', trace_parent=parent)
有关其他示例,请参阅 TraceParent
API。
使用 with_transaction
或 with_span
启动一个新的事务或跨度,作为传入事务或跨度的子级。
示例
使用 RUM 进行分布式跟踪edit
可能需要一些额外的设置才能将请求与实时用户监控 (RUM) 代理正确关联。
有关启用跨域请求、设置服务器配置和使用动态生成的 HTML 的信息,请参阅 RUM 分布式跟踪指南。