链路传播(Propagate)机制及使用场景

2023年 7月 14日 38.4k 0

服务间链路追踪传播机制是指在微服务架构中,通过记录和跟踪服务之间的请求和响应信息,来实现对服务间链路的追踪和监控。这种机制可以帮助开发人员快速定位服务间出现的问题,并进行优化和调整。

具体来说,服务间链路追踪传播机制可以通过在每个服务的请求和响应中添加唯一标识符来实现。当一个服务发送请求到另一个服务时,它会将自己的唯一标识符添加到请求头中,并发送给目标服务。目标服务收到请求后,会将请求头中的唯一标识符复制到响应头中,并返回给调用方。调用方收到响应后,可以通过唯一标识符来追踪服务间的调用链路。

为了实现服务间链路追踪传播机制,通常会使用一些开源工具或框架,比如OpenTelemetryDataDogZipkinSkyWalking 等。这些工具可以自动记录服务间的请求和响应信息,并提供可视化的界面来帮助开发人员进行调试和优化。

通常,我们会将这种特殊标识的请求头称之为传播协议。

常见传播协议

不同的 APM 工具支持一种或者多种传播协议,以便更好的服务于应用。

  • b3
  • uber
  • sw8
  • w3c
  • datadog
  • ot

img-20230614182217.png

OpenTelemetry 除了原生支持 otw3cb3uber几种协议外,通过:opentelemetry-collector-contrib 支持 sw8datadog传播协议。

B3 协议

B3 传播协议是第一个链路追踪协议,这里重点讲述 B3 传播协议相关内容。

B3 有两种编码:Single Header 和 Multiple Header。

  • 多个标头编码 X-B3-在跟踪上下文中使用每个项目的前缀标头
  • 单个标头将上下文分隔为一个名为 b3. 提取字段时,单头变体优先于多头变体。

这是一个使用多个标头编码的示例流程,假设 HTTP 请求带有传播的跟踪:

img-20230614153722.png

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-Sampledtruefalse,而不是10。虽然您不应该将X-B3-Sampled编码为truefalse,但宽松的实现可能会接受它们。

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论