服务间链路追踪传播机制是指在微服务架构中,通过记录和跟踪服务之间的请求和响应信息,来实现对服务间链路的追踪和监控。这种机制可以帮助开发人员快速定位服务间出现的问题,并进行优化和调整。
具体来说,服务间链路追踪传播机制可以通过在每个服务的请求和响应中添加唯一标识符来实现。当一个服务发送请求到另一个服务时,它会将自己的唯一标识符添加到请求头中,并发送给目标服务。目标服务收到请求后,会将请求头中的唯一标识符复制到响应头中,并返回给调用方。调用方收到响应后,可以通过唯一标识符来追踪服务间的调用链路。
为了实现服务间链路追踪传播机制,通常会使用一些开源工具或框架,比如
OpenTelemetry
、DataDog
、Zipkin
、SkyWalking
等。这些工具可以自动记录服务间的请求和响应信息,并提供可视化的界面来帮助开发人员进行调试和优化。
通常,我们会将这种特殊标识的请求头称之为传播协议。
常见传播协议
不同的 APM 工具支持一种或者多种传播协议,以便更好的服务于应用。
- b3
- uber
- sw8
- w3c
- datadog
- ot
OpenTelemetry 除了原生支持 ot
、w3c
、 b3
和uber
几种协议外,通过:opentelemetry-collector-contrib
支持 sw8
和datadog
传播协议。
B3 协议
B3 传播协议是第一个链路追踪协议,这里重点讲述 B3 传播协议相关内容。
B3 有两种编码:Single Header 和 Multiple Header。
- 多个标头编码 X-B3-在跟踪上下文中使用每个项目的前缀标头
- 单个标头将上下文分隔为一个名为 b3. 提取字段时,单头变体优先于多头变体。
这是一个使用多个标头编码的示例流程,假设 HTTP 请求带有传播的跟踪:
Multiple Headers
需要配合多个 header key 使用。
TraceId
X-B3-TraceId
标头编码为 32 或 16 个低十六进制字符。例如,128 位 TraceId 标头:X-B3-TraceId:463ac35c9f6413ad48485a3953bb6124
。除非仅传播“采样状态”,否则需要 X-B3-TraceId
标头。
SpanId
X-B3-SpanId
标头编码为16个较低的十六进制字符。例如:X-B3-SpanId:a2fb4a1d1a96d312
。除非仅传播“采样状态”,否则需要 X-B3-SpanId
标头。
ParentSpanId
X-B3-ParentSpanId
标头可能存在于子跨度上,而在根跨度上必须不存在。它被编码为16个低十六进制字符。例如,ParentSpanId
标头可能看起来像:X-B3-ParentSpanId:00020000000000001
Sampling State
接受采样决定编码为X-B3-Sampled:1
,拒绝编码为X-B3-Sampled:0
。缺席意味着将决定推迟到该报头的接收方。例如:X-B3-Sampled:1
。
注:在编写本规范之前,一些示踪剂传播X-B3-Sampled
为true
或false
,而不是1
或0
。虽然您不应该将X-B3-Sampled
编码为true
或false
,但宽松的实现可能会接受它们。
Debug Flag
调试编码为X-B3-Flags: 1
。可以忽略 Absent 或任何其他值。调试意味着接受决定,所以不要同时发送X-B3-Sampled
标头。
Single Header
Single Header
只有一个header 为b3
的 key 作为标记。
b3={TraceId}-{SpanId}-{SamplingState}-{ParentSpanId}
如
b3: 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-d
实现场景
目前主要有两大实现场景:
- APM:主要是服务端之间传播
- RUM:用户端(web、小程序等)与服务端传播
APM
OpenTelemetry 传播器源码
OpenTelemetry 传播器源码
传播器接口:TextMapPropagator
opentelemetry-java
通过定义统一的接口TextMapPropagator
来实现各种 Propagate。
TextMapPropagator
默认提供了一个空实现noop()
。
TextMapPropagator
定义了三个接口方法:
- fields() : 规定了哪些字段作为传播器识别的对象
- inject():定义了传播器字段注入规则
- extract():定义传播器字段提取规则,返回
Context
对象
一些厂商有自己的 APM 标准,通过扩展TextMapPropagator
接口,实现对接厂商特定的传播器。
@ThreadSafe
public interface TextMapPropagator {
static TextMapPropagator composite(TextMapPropagator... propagators) {
return composite(Arrays.asList(propagators));
}
static TextMapPropagator composite(Iterable propagators) {
List propagatorsList = new ArrayList();
for (TextMapPropagator propagator : propagators) {
propagatorsList.add(propagator);
}
if (propagatorsList.isEmpty()) {
return NoopTextMapPropagator.getInstance();
}
if (propagatorsList.size() == 1) {
return propagatorsList.get(0);
}
return new MultiTextMapPropagator(propagatorsList);
}
static TextMapPropagator noop() {
return NoopTextMapPropagator.getInstance();
}
Collection fields();
void inject(Context context, @Nullable C carrier, TextMapSetter setter);
Context extract(Context context, @Nullable C carrier, TextMapGetter getter);
}
OtTracePropagator
OtTracePropagator
是 OpenTelemetry 特有的传播器,识别解析ot-
开头的header,部分源码如下:
@Immutable
public final class OtTracePropagator implements TextMapPropagator {
static final String TRACE_ID_HEADER = "ot-tracer-traceid";
static final String SPAN_ID_HEADER = "ot-tracer-spanid";
static final String SAMPLED_HEADER = "ot-tracer-sampled";
static final String PREFIX_BAGGAGE_HEADER = "ot-baggage-";
private static final Collection FIELDS =
Collections.unmodifiableList(Arrays.asList(TRACE_ID_HEADER, SPAN_ID_HEADER, SAMPLED_HEADER));
....
@Override
public Collection fields() {
return FIELDS;
}
@Override
public void inject(Context context, @Nullable C carrier, TextMapSetter setter) {
if (context == null || setter == null) {
return;
}
SpanContext spanContext = Span.fromContext(context).getSpanContext();
if (!spanContext.isValid()) {
return;
}
// Lightstep trace id MUST be 64-bits therefore OpenTelemetry trace id is truncated to 64-bits
// by retaining least significant (right-most) bits.
setter.set(
carrier, TRACE_ID_HEADER, spanContext.getTraceId().substring(TraceId.getLength() / 2));
setter.set(carrier, SPAN_ID_HEADER, spanContext.getSpanId());
setter.set(carrier, SAMPLED_HEADER, String.valueOf(spanContext.isSampled()));
// Baggage is only injected if there is a valid SpanContext
Baggage baggage = Baggage.fromContext(context);
if (!baggage.isEmpty()) {
// Metadata is not supported by OpenTracing
baggage.forEach(
(key, baggageEntry) ->
setter.set(carrier, PREFIX_BAGGAGE_HEADER + key, baggageEntry.getValue()));
}
}
@Override
public Context extract(Context context, @Nullable C carrier, TextMapGetter getter) {
if (context == null) {
return Context.root();
}
if (getter == null) {
return context;
}
String incomingTraceId = getter.get(carrier, TRACE_ID_HEADER);
String traceId =
incomingTraceId == null
? TraceId.getInvalid()
: StringUtils.padLeft(incomingTraceId, MAX_TRACE_ID_LENGTH);
String spanId = getter.get(carrier, SPAN_ID_HEADER);
String sampled = getter.get(carrier, SAMPLED_HEADER);
SpanContext spanContext = buildSpanContext(traceId, spanId, sampled);
if (!spanContext.isValid()) {
return context;
}
Context extractedContext = context.with(Span.wrap(spanContext));
// Baggage is only extracted if there is a valid SpanContext
if (carrier != null) {
BaggageBuilder baggageBuilder = Baggage.builder();
for (String key : getter.keys(carrier)) {
if (!key.startsWith(PREFIX_BAGGAGE_HEADER)) {
continue;
}
String value = getter.get(carrier, key);
if (value == null) {
continue;
}
baggageBuilder.put(key.replace(OtTracePropagator.PREFIX_BAGGAGE_HEADER, ""), value);
}
Baggage baggage = baggageBuilder.build();
if (!baggage.isEmpty()) {
extractedContext = extractedContext.with(baggage);
}
}
return extractedContext;
}
...
}
初始化传播器
opentelemetry-java
通过PropagatorConfiguration
对象对Propagate
进行初始化操作,源码如下:
final class PropagatorConfiguration {
private static final List DEFAULT_PROPAGATORS = Arrays.asList("tracecontext", "baggage");
static ContextPropagators configurePropagators(
ConfigProperties config,
ClassLoader serviceClassLoader,
BiFunction