5. 传播
传播是确保从同一根节点开始的所有操作都收集到同一跟踪中的必需品。最常见的传播方法是在发送远程过程调用请求时,从客户端复制跟踪上下文,并在接收方服务器接收它。
例如,当下游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 环境中,您可能希望传递请求 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-Ray可以正确共存,请传递其追踪标题,如以下示例所示:
tracingBuilder.propagationFactory(
ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, "x-amzn-trace-id")
);
在Spring Cloud Sleuth中,Tracing.newBuilder()处的所有跟踪构建器元素都定义为bean。所以,如果你想传递一个自定义PropagationFactory,你只需要创建一个该类型的bean,我们就会把它设置在Tracingbean中。 |
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()
);
Later, you can call the following code to affect the country code of the current trace context:
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时,您必须传递行李密钥列表。 以下是实现此功能的属性。 使用 |
为了自动设置行李值到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. 提取传播上下文
The TraceContext.Extractor<C> 从传入的请求或消息中读取跟踪标识符和采样状态。
载体通常是请求对象或头信息。
此工具在标准仪器仪表中使用(例如HttpServerHandler),也可以用于自定义远程过程调用或消息传递代码。
TraceContextOrSamplingFlags 通常只与 Tracer.nextSpan(extracted) 一起使用,除非您需要在客户端和服务器之间共享 span ID。
5.1.3. 在客户端和服务器之间共享 span IDs
一个典型的仪器模式是在 RPC 的服务器端创建一个表示服务器端的跨度。 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。
<p>以下图片展示了AWS传播的一个例子:</p>
┌───────────────────┐ ┌───────────────────┐
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,其中一种是以下列之一:
-
TraceContextif trace and span IDs were present. -
TraceIdContext如果存在跟踪 ID,但不存在跨度 ID。 -
SamplingFlags如果没有标识符存在。
一些 Propagation 实现会在提取点携带额外数据(例如,读取传入的头)到注入(例如,写传出的头)之间传递。例如,它可能会携带一个请求ID。
当实现有额外数据时,它们会按以下方式处理:
-
如果提取出一个
TraceContext,则添加额外数据作为TraceContext.extra()。 -
否则,将其添加为
0,它1处理。