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
会将foo
Baggage 的值设置为 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()
。