On this page
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-code和x-baggage-user-id的形式在网络上发送country-code和user-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()。