52. 传播

需要进行传播以确保源自同一根的活动在同一迹线中收集在一起。最常见的传播方法是通过向接收它的服务器发送 RPC 请求来从 client 复制 trace context。

例如,当进行下游 HTTP 调用时,其 trace context 被编码为 request headers 并随之一起发送,如下图所示:

Client Span                                                Server Span
┌──────────────────┐                                       ┌──────────────────┐
│                  │                                       │                  │
│   TraceContext   │           Http Request Headers        │   TraceContext   │
│ ┌──────────────┐ │          ┌───────────────────┐        │ ┌──────────────┐ │
│ │ TraceId      │ │          │ X─B3─TraceId      │        │ │ TraceId      │ │
│ │              │ │          │                   │        │ │              │ │
│ │ ParentSpanId │ │ Extract  │ X─B3─ParentSpanId │ Inject │ │ ParentSpanId │ │
│ │              ├─┼─────────>│                   ├────────┼>│              │ │
│ │ SpanId       │ │          │ X─B3─SpanId       │        │ │ SpanId       │ │
│ │              │ │          │                   │        │ │              │ │
│ │ Sampled      │ │          │ X─B3─Sampled      │        │ │ Sampled      │ │
│ └──────────────┘ │          └───────────────────┘        │ └──────────────┘ │
│                  │                                       │                  │
└──────────────────┘                                       └──────────────────┘

上面的名称来自B3 传播,它是 built-in 到 Brave,并且在许多语言和框架中都有 implementations。

大多数用户使用 framework 拦截器来自动传播。接下来的两个示例显示了如何对 client 和服务器起作用。

以下 example 显示了 client-side 传播如何工作:

@Autowired Tracing tracing;

// configure a function that injects a trace context into a request
injector = tracing.propagation().injector(Request.Builder::addHeader);

// before a request is sent, add the current span's context to it
injector.inject(span.context(), request);

以下 example 显示了 server-side 传播如何工作:

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// configure a function that extracts the trace context from a request
extractor = tracing.propagation().extractor(Request::getHeader);

// when a server receives a request, it joins or starts a new trace
span = tracer.nextSpan(extractor.extract(request));

52.1 传播额外的字段

有时您需要传播额外的字段,例如请求 ID 或备用跟踪 context。例如,如果您处于 Cloud Foundry 环境中,则可能需要传递请求 ID,如以下 example 所示:

// when you initialize the builder, define the extra field you want to propagate
Tracing.newBuilder().propagationFactory(
  ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-vcap-request-id")
);

// later, you can tag that request ID or use it in log correlation
requestId = ExtraFieldPropagation.get("x-vcap-request-id");

您可能还需要传播未使用的跟踪 context。例如,您可能位于 Amazon Web Services 环境中,但不会将数据报告给 X-Ray。要确保 X-Ray 能正确 co-exist,pass-through 其跟踪标头,如下面的示例所示:

tracingBuilder.propagationFactory(
  ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);

在 Spring Cloud Sleuth 中,跟踪构建器Tracing.newBuilder()的所有元素都定义为 beans。所以如果你想传递一个自定义的PropagationFactory,你就可以创建一个这种类型的 bean,我们将它设置在Tracing bean 中。

52.1.1 前缀字段

如果它们遵循 common pattern,您也可以为字段添加前缀。以下 example 显示如何传播x-vcap-request-id字段 as-is,但分别将country-codeuser-id字段作为x-baggage-country-codex-baggage-user-id发送:

Tracing.newBuilder().propagationFactory(
  ExtraFieldPropagation.newFactoryBuilder(B3Propagation.FACTORY)
                       .addField("x-vcap-request-id")
                       .addPrefixedFields("x-baggage-", Arrays.asList("country-code", "user-id"))
                       .build()
);

稍后,您可以调用以下 code 来影响当前 trace context 的国家 code:

ExtraFieldPropagation.set("x-country-code", "FO");
String countryCode = ExtraFieldPropagation.get("x-country-code");

或者,如果您对 trace context 有一个 reference,则可以显式使用它,如下面的 example 所示:

ExtraFieldPropagation.set(span.context(), "x-country-code", "FO");
String countryCode = ExtraFieldPropagation.get(span.context(), "x-country-code");

与以前版本的 Sleuth 不同的是,使用 Brave,您必须传递 baggage 键列表。有两个 properties 来实现这一目标。使用spring.sleuth.baggage-keys,您可以为 HTTP calls 设置以baggage-为前缀的键,为消息传递设置baggage_。您还可以使用spring.sleuth.propagation-keys property 传递列入白名单但没有任何前缀的前缀键列表。请注意,标题键前面没有x-

52.1.2 提取传播的 Context

TraceContext.Extractor<C>从传入的请求或消息中读取跟踪标识符和采样状态。运营商通常是请求 object 或 headers。

此实用程序用于标准检测(例如`HttpServerHandler``),但也可用于自定义 RPC 或消息传递 code。

TraceContextOrSamplingFlags通常仅与Tracer.nextSpan(extracted)一起使用,除非您在 client 和服务器之间共享 span ID。

52.1.3 在 Client 和 Server 之间共享 span ID

正常的检测 pattern 是创建一个表示 RPC 服务器端的 span。当应用于传入的 client 请求时,Extractor.extract可能会 return 一个完整的 trace context。 Tracer.joinSpan尝试继续此跟踪,如果支持则使用相同的 span ID,否则创建子 span。当共享 span ID 时,报告的数据包含 flag 这样说。

下图显示了 B3 传播的示例:

┌───────────────────┐      ┌───────────────────┐
 Incoming Headers             │   TraceContext    │      │   TraceContext    │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ X─B3-TraceId      │─────────┼─┼> TraceId      │ │──────┼─┼> TraceId      │ │
│                   │         │ │               │ │      │ │               │ │
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
│                   │         │ │               │ │      │ │               │ │
│ X─B3-SpanId       │─────────┼─┼> SpanId       │ │──────┼─┼> SpanId       │ │
└───────────────────┘         │ │               │ │      │ │               │ │
                              │ │               │ │      │ │  Shared: true │ │
                              │ └───────────────┘ │      │ └───────────────┘ │
                              └───────────────────┘      └───────────────────┘

某些传播系统仅转发 parent span ID,在Propagation.Factory.supportsJoin() == false时检测到。在这种情况下,始终配置新的 span ID,并且传入的 context 确定 parent ID。

下图显示了 AWS 传播的一个示例:

┌───────────────────┐      ┌───────────────────┐
 x-amzn-trace-id              │   TraceContext    │      │   TraceContext    │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ Root              │─────────┼─┼> TraceId      │ │──────┼─┼> TraceId      │ │
│                   │         │ │               │ │      │ │               │ │
│ Parent            │─────────┼─┼> SpanId       │ │──────┼─┼> ParentSpanId │ │
└───────────────────┘         │ └───────────────┘ │      │ │               │ │
                              └───────────────────┘      │ │  SpanId: New  │ │
                                                         │ └───────────────┘ │
                                                         └───────────────────┘

注意:某些 span 记者不支持共享 span ID。例如,如果设置Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive),则应通过设置Tracing.Builder.supportsJoin(false)来禁用连接。这样做会强制Tracer.joinSpan()上的新子 span。

52.1.4 实施传播

TraceContext.Extractor<C>Propagation.Factory插件实现。在内部,此 code 使用以下之一创建 union 类型TraceContextOrSamplingFlags:* TraceContext如果存在 trace 和 span ID。 * TraceIdContext如果存在跟踪 ID 但 span ID 不存在。 * SamplingFlags如果没有标识符。

一些Propagation implementations 从提取点(例如,读取传入的 headers)到注入(例如,编写传出的 headers)携带额外的数据。例如,它可能带有请求 ID。当 implementations 有额外数据时,它们按如下方式处理:*如果提取TraceContext,则将额外数据添加为TraceContext.extra()。 *否则,将其添加为TraceContextOrSamplingFlags.extra(),其中Tracer.nextSpan处理。