正在加载

链路

Elastic Stack Serverless

链路是具有公共根的 事务span 的集合。 每个链路跟踪单个请求的完整性。 它描述了单个逻辑操作产生的各个操作及其因果关系。

当链路通过多个服务时(这在微服务架构中很常见),它被称为**分布式链路**。 分布式链路包含跨多个分布式组件的操作,跨越进程、网络和安全边界。

分布式链路追踪使您能够通过跟踪整个请求(从前端服务上的初始 Web 请求到后端服务上发出的数据库查询)来分析整个微服务架构中的性能。

跟踪请求在服务中的传播过程,可以全面了解应用程序花费时间的位置、发生错误的位置以及形成瓶颈的位置。 分布式链路追踪消除了各个服务的数据孤岛,并揭示了服务边界之外发生的事情。

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

分布式链路追踪的工作原理是将自定义 traceparent HTTP 标头注入到传出请求中。 此标头包含诸如 trace-id 之类的信息,用于标识当前链路;以及 parent-id,用于标识传入请求上的当前 span 的父级,或传出请求上的当前 span。

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

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

How traceparent propagation works

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

How traceparent propagation works

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

How traceparent propagation works

所有 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 Applications UI

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

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

不适用。

  1. 使用 StartTransaction 启动事务,或使用 StartSpan 启动 span。
  2. 获取活动的 TraceContext
  3. TraceContext 发送到接收服务。

示例

transaction := apm.DefaultTracer().StartTransaction("GET /", "request")
traceContext := transaction.TraceContext()

// Send TraceContext to receiving service
traceparent := apmhttp.FormatTraceparentHeader(traceContext)
tracestate := traceContext.State.String()
  1. 启动事务
  2. 从当前事务获取 TraceContext
  3. TraceContexttracestate 格式化为 traceparent 标头。

该代理将自动将链路标头注入到使用 URLSessions 的网络请求中,但是如果您使用非标准网络库,您可能需要手动注入它们。 这将使用 OpenTelemetry API 完成

  1. 创建一个 Setter
  2. 根据 Open Telemetry 标准 创建一个 Span
  3. 将链路上下文注入到标头字典中
  4. 按照您的网络库的步骤完成网络请求。 确保在请求成功或失败时调用 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
...
span.end()
  1. 使用 startTransaction 启动事务,或使用 startSpan 启动 span。
  2. 使用 injectTraceHeaderstraceparent 标头注入到请求对象中
  3. 发出网络请求

手动检测 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();
  }
}
  1. 创建一个表示外部调用的 span
  2. traceparent 标头注入到请求对象中
  3. 结束 span
  1. 使用 CurrentTransactionCurrentSpan 序列化活动事务或 span 的分布式链路追踪上下文。
  2. 将序列化的上下文发送到接收服务。

示例

string outgoingDistributedTracingData =
    (Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData
        ?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData)?.SerializeToString();
// Now send `outgoingDistributedTracingData` to the receiving service
  1. 使用 apm.startTransaction() 启动事务,或使用 apm.startSpan() 启动 span。
  2. 使用 currentTraceparent 获取启动的事务/span 的序列化 traceparent 字符串。
  3. traceparent 进行编码,并在常规请求中将其发送到接收服务。

使用原始 UDP 在两个服务 A 和 B 之间进行通信的示例

agent.startTransaction('my-service-a-transaction');
const traceparent = agent.currentTraceparent;
sendMetadata(`traceparent: ${traceparent}\n`);
  1. 启动事务
  2. 获取当前的 traceparent
  3. traceparent 作为标头发送到服务 B。
  1. 在客户端(即发送请求的一方)获取当前分布式链路追踪上下文。
  2. 将当前分布式链路追踪上下文序列化为请求传输支持的格式,并将其发送到服务器端(即接收请求的一方)。

示例

$distDataAsString = ElasticApm::getSerializedCurrentDistributedTracingData();
  1. 获取序列化为字符串的当前分布式链路追踪数据
  1. 使用 begin_transaction() 启动事务。
  2. 获取活动事务的 trace_parent
  3. trace_parent 发送到接收服务。

示例

client.begin_transaction('new-transaction')<1>

elasticapm.get_trace_parent_header('new-transaction')

# Send `trace_parent_str` to another service
  1. 启动新事务
  2. 返回当前事务的 TraceParent 对象的字符串表示形式
  1. 使用 with_span 启动 span。
  2. 获取活动的 TraceContext
  3. TraceContext 发送到接收服务。
ElasticAPM.with_span "Name" do |span|
  header = span.trace_context.traceparent.to_header
  # send the TraceContext Header to a receiving service...
end
  1. 启动 span
  2. 获取 TraceContext

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

不适用。

  1. 使用 ParseTraceparentHeaderParseTracestateHeader 解析传入的 TraceContext
  2. 使用 StartTransactionOptionsStartSpanOptions,启动一个新的事务或 span 作为传入事务的子项。

示例

// 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)
  1. 解析 TraceParent 标头
  2. 解析 Tracestate 标头
  3. 设置父跟踪上下文
  4. 启动一个新的事务,作为接收到的 TraceContext 的子项

不适用。

  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();
    }
}
  1. 创建一个事务,作为远程父级的子项
  2. 激活事务
  3. 命名事务
  4. 添加事务类型
  5. 最终,结束事务

反序列化传入的分布式跟踪上下文,并将其传递给任何 StartTransactionCaptureTransaction API — 它们都有一个可选的 DistributedTracingData 参数。 这将创建一个新的事务或 span 作为传入跟踪上下文的子项。

启动新事务的示例

var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction",
     DistributedTracingData.TryDeserializeFromString(serializedDistributedTracingData));
  1. 在接收服务中解码并存储 traceparent
  2. traceparent 作为 childOf 选项传递,以手动启动一个新事务,作为接收到的 traceparent 的子项,使用 apm.startTransaction()

通过原始 UDP 接收 traceparent 的示例

const traceparent = readTraceparentFromUDPPacket()
agent.startTransaction('my-service-b-transaction', { childOf: traceparent })
  1. 从传入的请求中读取 traceparent
  2. 使用 traceparent 初始化一个新事务,该事务是原始 traceparent 的子项。
  1. 在服务器端接收分布式跟踪数据。
  2. 使用代理的公共 API 开始一个新事务。 例如,使用 ElasticApm::beginCurrentTransaction 并将接收到的分布式跟踪数据(序列化为字符串)作为参数传递。 这将创建一个新的事务,作为传入跟踪上下文的子项。
  3. 不要忘记最终在服务器端结束事务。

示例

$receiverTransaction = ElasticApm::beginCurrentTransaction(
    'GET /data-api',
    'data-layer',
    /* timestamp */ null,
    $distDataAsString
);
  1. 启动新事务
  2. 传入接收到的分布式跟踪数据(序列化为字符串)

在接收服务中创建了新的事务后,您可以创建子 span,或者使用您通常使用的任何其他代理 API 方法。

  1. 从字符串或 HTTP 标头创建 TraceParent 对象。
  2. 通过传入 TraceParent 对象,启动一个新的事务,作为 TraceParent 的子项。

使用 HTTP 标头的示例

parent = elasticapm.trace_parent_from_headers(headers_dict)
client.begin_transaction('processors', trace_parent=parent)
  1. 从形成字典的 HTTP 标头创建 TraceParent 对象
  2. 开始一个新事务,作为接收到的 TraceParent 的子项
提示

有关其他示例,请参见 TraceParent API

使用 with_transactionwith_span,启动一个新的事务或 span 作为传入事务或 span 的子项。

示例

# env being a Rack env
context = ElasticAPM::TraceContext.parse(env: env)

ElasticAPM.with_transaction("Do things", trace_context: context) do
  ElasticAPM.with_span("Do nested thing", trace_context: context) do
  end
end
  1. 解析传入的 TraceContext
  2. 创建一个事务,作为传入的 TraceContext 的子项
  3. 创建 span 作为新创建的事务的子项。 在这里,trace_context 是可选的,因为 span 在没有传递 trace_context 的情况下,将自动创建为父事务的 TraceContext 的子项。

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

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

© . All rights reserved.