5. 传播
需要传播以确保源自同一根的活动在同一跟踪中一起收集。 最常见的传播方法是通过向接收 RPC 请求的服务器发送 RPC 请求来从客户端复制跟踪上下文。
例如,当进行下游 HTTP 调用时,其跟踪上下文将编码为请求标头并随之发送,如下图所示:
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 内置的,并有多种语言和框架的实现。
大多数用户使用框架拦截器来自动传播。 接下来的两个示例显示了它如何适用于客户端和服务器。
以下示例显示了客户端传播的工作原理:
@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));
5.1. 传播额外的字段
有时需要传播额外的字段,例如请求 ID 或备用跟踪上下文。 例如,如果您位于 Cloud Foundry 环境中,则可能需要传递请求标识,如以下示例所示:
// 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-Ray 可以正确共存,请传递其跟踪标头,如以下示例所示:
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);
在 Spring Cloud Sleuth 中,跟踪构建器的所有元素Tracing.newBuilder() 被定义为 bean。所以如果你想传递一个自定义PropagationFactory ,就够了
以便您创建该类型的 bean,我们将在Tracing 豆。 |
5.1.1. 前缀字段
如果它们遵循通用模式,您还可以为字段添加前缀。
以下示例演示如何传播x-vcap-request-id
字段按原样发送country-code
和user-id
线上的字段作为x-baggage-country-code
和x-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()
);
稍后,您可以调用以下代码来影响当前跟踪上下文的国家/地区代码:
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");
与之前版本的 Sleuth 的不同之处在于,在 Brave 中,您必须通过行李钥匙列表。
有以下属性可以实现这一点。
使用spring.sleuth.baggage-keys ,则设置前缀为baggage- 对于 HTTP 调用和baggage_ 用于消息传递。
您还可以使用spring.sleuth.propagation-keys 属性以传递一个前缀键列表,这些键传播到远程服务,没有任何前缀。
您还可以使用spring.sleuth.local-keys 属性来传递列表键,这些键将在本地传播,但不会通过网络传播。
请注意,没有x- 在标题键前面。 |
为了自动将行李值设置为 Slf4j 的 MDC,您必须将
这spring.sleuth.log.slf4j.whitelisted-mdc-keys
属性列表为
行李和传播密钥。例如spring.sleuth.log.slf4j.whitelisted-mdc-keys=foo
将设置foo
行李进入 MDC。
请注意,从下一个下游跟踪上下文开始,额外字段将传播并添加到 MDC。立即 在当前跟踪上下文中将额外的字段添加到 MDC,将该字段配置为在更新时刷新:
@Bean
ScopeDecorator mdcScopeDecorator() {
BaggageField countryCodeField = BaggageField.create("x-country-code");
return MDCScopeDecorator.newBuilder()
.clear()
.add(SingleCorrelationField.newBuilder(countryCodeField)
.flushOnUpdate()
.build())
.build();
}
请记住,向 MDC 添加条目会大大降低应用程序的性能! |
如果要将行李条目添加为标签,以便能够通过行李条目搜索跨度,可以将spring.sleuth.propagation.tag.whitelisted-keys
以及列入白名单的行李钥匙列表。要禁用该功能,您必须将spring.sleuth.propagation.tag.enabled=false
财产。
5.1.2. 提取传播的上下文
这TraceContext.Extractor<C>
从传入请求或消息中读取跟踪标识符和采样状态。
载体通常是请求对象或标头。
此实用程序用于标准仪器(例如HttpServerHandler
),但也可用于自定义 RPC 或消息传递代码。
TraceContextOrSamplingFlags
通常仅与Tracer.nextSpan(extracted)
,除非你是
在客户端和服务器之间共享跨度 ID。
5.1.3. 在客户端和服务器之间共享跨度 ID
正常的检测模式是创建一个表示 RPC 服务器端的 span。Extractor.extract
应用于传入客户端请求时,可能会返回完整的跟踪上下文。Tracer.joinSpan
尝试继续此跟踪,使用相同的跨度 ID(如果支持)或创建子跨度
如果没有。共享跨度 ID 时,报告的数据会包含一个标志,说明这一点。
下图显示了 B3 传播的示例:
┌───────────────────┐ ┌───────────────────┐
Incoming Headers │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ X─B3-TraceId │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-ParentSpanId │─────────┼─┼> ParentSpanId │ │──────┼─┼> ParentSpanId │ │
│ │ │ │ │ │ │ │ │ │
│ X─B3-SpanId │─────────┼─┼> SpanId │ │──────┼─┼> SpanId │ │
└───────────────────┘ │ │ │ │ │ │ │ │
│ │ │ │ │ │ Shared: true │ │
│ └───────────────┘ │ │ └───────────────┘ │
└───────────────────┘ └───────────────────┘
某些传播系统仅转发父跨度 ID,在Propagation.Factory.supportsJoin() == false
.
在这种情况下,始终会预配新的跨度 ID,传入上下文确定父 ID。
下图显示了 AWS 传播的示例:
┌───────────────────┐ ┌───────────────────┐
x-amzn-trace-id │ TraceContext │ │ TraceContext │
┌───────────────────┐(extract)│ ┌───────────────┐ │(join)│ ┌───────────────┐ │
│ Root │─────────┼─┼> TraceId │ │──────┼─┼> TraceId │ │
│ │ │ │ │ │ │ │ │ │
│ Parent │─────────┼─┼> SpanId │ │──────┼─┼> ParentSpanId │ │
└───────────────────┘ │ └───────────────┘ │ │ │ │ │
└───────────────────┘ │ │ SpanId: New │ │
│ └───────────────┘ │
└───────────────────┘
注意:某些跨度报告器不支持共享跨度 ID。
例如,如果您将Tracing.Builder.spanReporter(amazonXrayOrGoogleStackdrive)
,您应该通过设置Tracing.Builder.supportsJoin(false)
.
这样做会强制新的子跨度Tracer.joinSpan()
.
5.1.4. 实现传播
TraceContext.Extractor<C>
由Propagation.Factory
插件。
在内部,此代码创建联合类型TraceContextOrSamplingFlags
,并具有以下选项之一:
-
TraceContext
如果存在跟踪和跨度 ID。 -
TraceIdContext
如果存在跟踪 ID,但不存在跨度 ID。 -
SamplingFlags
如果不存在标识符。
一些Propagation
实现从提取点(例如,读取传入标头)到注入(例如,写入传出标头)携带额外的数据。
例如,它可能带有请求 ID。
当实现有额外数据时,它们会按如下方式处理它:
-
如果
TraceContext
被提取时,将额外的数据添加为TraceContext.extra()
. -
否则,将其添加为
TraceContextOrSamplingFlags.extra()
哪Tracer.nextSpan
处理。