54. Propagation

需要进行传播以确保源自同一根的Active被收集到同一条迹线中。最常见的传播方法是通过将 RPC 请求发送到接收它的服务器来从 Client 端复制跟踪上下文。

例如,当进行下游 HTTP 调用时,其跟踪上下文被编码为请求 Headers,并与请求 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 Propagation,它是 Brave 内置的,并具有许多语言和框架的实现。

大多数用户使用框架拦截器来自动化传播。接下来的两个示例显示了这对于 Client 端和服务器可能如何工作。

以下示例显示了 Client 端传播如何工作:

@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);

以下示例显示了服务器端传播的工作方式:

@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));

54.1 传播其他字段

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

// 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");

您可能还需要传播未使用的跟踪上下文。例如,您可能处于 Amazon Web Services 环境中,但没有向 X-Ray 报告数据。为了确保 X 射线可以正确共存,请传递其跟踪 Headers,如以下示例所示:

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

Tip

在 Spring Cloud Sleuth 中,跟踪构建器Tracing.newBuilder()的所有元素都定义为 bean。因此,如果要传递自定义PropagationFactory,就足以创建该类型的 bean,我们将在Tracing bean 中进行设置。

54.1.1 前缀字段

如果它们遵循通用模式,则还可以在字段前面加上前缀。以下示例显示了如何按原样传播x-vcap-request-id字段,但如何分别以x-baggage-country-codex-baggage-user-id的形式在网络上发送country-codeuser-id字段:

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

以后,您可以调用以下代码来影响当前跟踪上下文的国家/地区代码:

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

或者,如果您有对跟踪上下文的引用,则可以显式使用它,如以下示例所示:

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

Tip

与以前版本的 Sleuth 的不同之处在于,使用 Brave,您必须传递 Baggage 钥匙清单。有两个属性可以实现此目的。使用spring.sleuth.baggage-keys,您可以设置密钥,这些密钥的前缀为baggage-(用于 HTTP 调用)和baggage_(用于消息传递)。您还可以使用spring.sleuth.propagation-keys属性来传递已列入白名单且没有任何前缀的前缀键列表。请注意,Headers 键前面没有x-

为了自动将 Baggage 值设置为 Slf4j 的 MDC,您必须使用列入白名单的 Baggage 和传播键的列表来设置spring.sleuth.log.slf4j.whitelisted-mdc-keys属性。例如。 spring.sleuth.log.slf4j.whitelisted-mdc-keys=foo会将fooBaggage 的值设置为 MDC。

Tip

请记住,将条目添加到 MDC 可能会大大降低应用程序的性能!

如果要将 Baggage 条目添加为标签,以便可以通过 Baggage 条目搜索 Span,则可以将白名单中的 Baggage 钥匙列表设置为spring.sleuth.propagation.tag.whitelisted-keys的值。要禁用此功能,您必须传递spring.sleuth.propagation.tag.enabled=false属性。

54.1.2 提取传播的上下文

TraceContext.Extractor<C>从传入的请求或消息中读取跟踪标识符和采样状态。载体通常是一个请求对象或 Headers。

此 Util 用于标准工具(例如HttpServerHandler),但也可以用于自定义 RPC 或消息传递代码。

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

54.1.3Client 端和服务器之间共享范围 ID

正常的检测模式是创建一个 Span,该 Span 代表 RPC 的服务器端。 Extractor.extract应用于传入的 Client 端请求时,可能会返回完整的跟踪上下文。 Tracer.joinSpan尝试 continue 此跟踪,如果支持,则使用相同的 SpanID,否则,创建一个子 Span。当 SpanID 被共享时,报告的数据包括这样的标记。

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

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

某些传播系统仅转发在Propagation.Factory.supportsJoin() == false时检测到的父范围 ID。在这种情况下,始终会提供新的 SpanID,并且传入上下文确定父 ID。

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

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

注意:某些 Span 报告程序不支持共享 SpanID。例如,如果设置Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive),则应通过设置Tracing.Builder.supportsJoin(false)禁用联接。这样做会在Tracer.joinSpan()上强制一个新的子 Span。

54.1.4 实施传播

TraceContext.Extractor<C>Propagation.Factory插件实现。在内部,此代码使用以下之一创建联合类型TraceContextOrSamplingFlags:* TraceContext如果存在跟踪和 SpanID。 * TraceIdContext(如果存在跟踪 ID,但不存在 SpanID)。 * SamplingFlags如果不存在标识符。

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