分布式追踪编辑

一个 trace 是一个 事务跨度 的集合,它们具有共同的根。每个 trace 跟踪单个请求的完整过程。当一个 trace 穿越多个服务时,就像微服务架构中常见的那样,它被称为分布式追踪。

为什么分布式追踪很重要?编辑

分布式追踪使您能够通过追踪请求的完整过程来分析整个微服务架构的性能,从前端服务的初始 Web 请求一直到后端服务执行的数据库查询。

跟踪请求在服务中的传播,可以提供应用程序在何处花费时间、错误发生的位置以及瓶颈形成位置的端到端视图。分布式追踪消除了各个服务的孤立数据,并揭示了服务边界之外发生的事情。

对于支持的技术,分布式追踪无需任何额外配置即可开箱即用。

分布式追踪的工作原理编辑

分布式追踪通过将自定义的 traceparent HTTP 标头注入到传出的请求中来实现。此标头包含信息,例如 trace-id,用于标识当前的追踪,以及 parent-id,用于标识当前跨度的父级,在传入请求中是当前跨度的父级,在传出请求中是当前跨度本身。

当服务处理请求时,它会检查此 HTTP 标头是否存在。如果不存在,服务会启动一个新的追踪。如果存在,服务会确保将当前操作添加为现有追踪的子级,并继续传播追踪。

追踪传播示例编辑

在此示例中,Elastic 的 Ruby 代理与 Elastic 的 Java 代理通信。两者都支持 traceparent 标头,并且追踪数据成功传播。

How traceparent propagation works

在此示例中,Elastic 的 Ruby 代理与 OpenTelemetry 的 Java 代理通信。两者都支持 traceparent 标头,并且追踪数据成功传播。

How traceparent propagation works

在此示例中,追踪遇到了一个不传播 traceparent 标头的中间件。分布式追踪结束,任何进一步的通信都将导致新的追踪。

How traceparent propagation works
W3C 追踪上下文规范编辑

所有 Elastic 代理现在都支持官方的 W3C 追踪上下文规范和 traceparent 标头。请参阅下表以了解所需的最低代理版本。

代理名称 代理版本

Go 代理

1.6

Java 代理

1.14

.NET 代理

1.3

Node.js 代理

3.4

PHP 代理

1.0

Python 代理

5.4

Ruby 代理

3.5

RUM 代理

5.0

旧版本的 Elastic 代理使用唯一的 elastic-apm-traceparent 标头。为了向后兼容,新版本的 Elastic 代理仍然支持此标头。

可视化分布式追踪编辑

APM 应用程序的时间线可视化提供了对应用程序每个追踪的视觉深入分析。

Distributed tracing in the APM UI

手动分布式追踪编辑

Elastic 代理会自动为支持的技术传播分布式追踪上下文。如果您的服务通过其他不支持的协议进行通信,您可以使用每个代理的 API 手动将分布式追踪上下文从发送服务传播到接收服务。

traceparent 标头添加到传出的请求编辑

发送服务必须将 traceparent 标头添加到传出的请求。

  1. 使用 startTransaction 启动事务,或使用 startSpan 启动跨度。
  2. 使用 injectTraceHeaderstraceparent 标头注入到请求对象中。

手动检测 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(); 
  }
}

创建一个表示外部调用的跨度

traceparent 标头注入到请求对象中

结束跨度

解析传入请求中的 traceparent 标头编辑

接收服务必须解析传入的 traceparent 标头,并作为接收上下文的子级启动一个新的事务或跨度。

  1. 使用 startTransactionWithRemoteParent() 作为传入事务的子级创建一个事务。
  2. 使用 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(); 
    }
}

创建远程父级事务的子事务

激活事务

命名事务

添加事务类型

最终,结束事务

使用 RUM 进行分布式跟踪edit

可能需要一些额外的设置才能将请求与实时用户监控 (RUM) 代理正确关联。

有关启用跨域请求、设置服务器配置和使用动态生成的 HTML 的信息,请参阅 RUM 分布式跟踪指南