50. Features

  • 将 trace 和 span ID 添加到 Slf4J MDC,因此您可以从 log 聚合器中的给定跟踪或 span 中提取所有日志,如以下 example 日志中所示:
2016-02-02 15:30:57.902  INFO [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:30:58.372 ERROR [bar,6bfd228dc00d216b,6bfd228dc00d216b,false] 23030 --- [nio-8081-exec-3] ...
2016-02-02 15:31:01.936  INFO [bar,46ab0d418373cbc9,46ab0d418373cbc9,false] 23030 --- [nio-8081-exec-4] ...

注意 MDC 的[appname,traceId,spanId,exportable]条目:

  • spanId:发生的特定操作的 ID。

  • appname:记录 span 的 application 的 name。

  • traceId:包含 span 的延迟图的 ID。

  • exportable:是否应将 log 导出为 Zipkin。你想什么时候 span 不能出口?当您想要在 Span 中包装某些操作并将其写入日志时。

  • 提供对 common 分布式跟踪数据模型的抽象:跟踪,spans(形成 DAG),注释和 key-value 注释。 Spring Cloud Sleuth 松散地基于 HTrace 但与 Zipkin(Dapper)兼容。

  • Sleuth 记录时序信息以帮助进行延迟分析。通过使用 sleuth,您可以查明 applications 中的延迟原因。

  • Sleuth 被写入不太多 log 并且不会导致 production application 崩溃。为此,Sleuth:

  • 传播有关您的调用图 in-band 和 rest out-of-band 的结构数据。

  • 包括诸如 HTTP 等层的固定式检测。

  • 包括一个用于管理卷的抽样 policy。

  • 可以向 Zipkin 系统报告查询和可视化。

  • 乐器 common 入口和出口点来自 Spring applications(servlet 过滤器,async endpoints,rest template,预定动作,消息 channels,Zuul 过滤器和 Feign client)。

  • Sleuth 包含用于跨 HTTP 或消息传递边界连接跟踪的默认逻辑。对于 example,HTTP 传播适用于 Zipkin-compatible request headers。

  • Sleuth 可以在进程之间传播 context(也称为 baggage)。因此,如果在 Span 上设置 baggage 元素,则会通过 HTTP 或消息传递将其下游发送到其他进程。

  • 提供一种创建或 continue spans 并通过 annotations 添加标记和日志的方法。

  • 如果spring-cloud-sleuth-zipkin在 classpath 上,则应用程序会生成并收集 Zipkin-compatible 跟踪。默认情况下,它通过 HTTP 将它们发送到 localhost(port 9411)上的 Zipkin 服务器。您可以通过设置spring.zipkin.baseUrl来配置服务的位置。

  • 如果您依赖spring-rabbit,您的应用程序会将跟踪发送到 RabbitMQ broker 而不是 HTTP。

  • 如果依赖spring-kafka并设置spring.zipkin.sender.type: kafka,则应用程序会将跟踪发送到 Kafka broker 而不是 HTTP。

spring-cloud-sleuth-stream已弃用,不应再使用。

如果使用 Zipkin,请通过设置spring.sleuth.sampler.probability配置 spans 导出的概率(默认值:0.1,即 10%)。否则,您可能会认为 Sleuth 无效,因为它省略了一些 spans。

始终设置 SLF4J MDC,并且回溯用户立即按照前面显示的 example 查看日志中的 trace 和 span ID。其他 logging 系统必须配置自己的格式化程序才能获得相同的结果。默认值如下:logging.pattern.level设置为%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}](对于 logback 用户,这是 Spring Boot feature)。如果您不使用 SLF4J,则不会自动应用此 pattern。

50.1 Brave 简介

从 version 2.0.0开始,Spring Cloud Sleuth 使用勇敢作为跟踪 library。为了您的方便,我们在这里嵌入了 Brave 的 docs 的一部分。

在绝大多数情况下,您只需使用 Sleuth 提供的 Brave 中的TracerSpanCustomizer beans。下面的文档概述了 Brave 是什么以及它是如何工作的。

Brave 是一个 library,用于捕获和报告有关分布式操作的延迟信息到 Zipkin。大多数用户不直接使用 Brave。他们使用 libraries 或框架而不是代表他们使用 Brave。

此模块包含一个跟踪器,用于创建和连接 spans,用于模拟潜在分布式工作的延迟。它还包括 libraries 以通过网络边界传播 trace context(对于 example,使用 HTTP headers)。

50.1.1 追踪

最重要的是,你需要brave.Tracer,配置为向 Zipkin 汇报

以下 example 设置通过 HTTP 将跟踪数据(spans)发送到 Zipkin(而不是 Kafka):

class MyClass {

    private final Tracer tracer;

    // Tracer will be autowired
    MyClass(Tracer tracer) {
        this.tracer = tracer;
    }

    void doSth() {
        Span span = tracer.newTrace().name("encode").start();
        // ...
    }
}

如果 span 包含长度超过 50 个字符的 name,则该 name 将被截断为 50 个字符。你的名字必须明确而具体。大名称会导致延迟问题,有时甚至会抛出 exceptions。

跟踪器创建并加入 spans,用于模拟潜在分布式工作的延迟。它可以采用采样来减少 process 期间的开销,减少发送到 Zipkin 的数据量,或两者兼而有之。

完成后,跟踪器报告数据返回到 Zipkin,如果未采样则不执行任何操作。启动 span 后,您可以注释感兴趣的 events 或添加包含详细信息或查找键的标签。

Spans 有一个 context,其中包含跟踪标识符,将 span 放在表示分布式操作的树中的正确位置。

50.1.2 本地追踪

当跟踪从未离开 process 的 code 时,运行它在一个范围的 span 中。

@Autowired Tracer tracer;

// Start a new trace or a span within an existing trace representing an operation
ScopedSpan span = tracer.startScopedSpan("encode");
try {
  // The span is in "scope" meaning downstream code such as loggers can see trace IDs
  return encoder.encode();
} catch (RuntimeException | Error e) {
  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
  throw e;
} finally {
  span.finish(); // always finish the span
}

当您需要更多 features 或更精细的控件时,请使用Span类型:

@Autowired Tracer tracer;

// Start a new trace or a span within an existing trace representing an operation
Span span = tracer.nextSpan().name("encode").start();
// Put the span in "scope" so that downstream code such as loggers can see trace IDs
try (SpanInScope ws = tracer.withSpanInScope(span)) {
  return encoder.encode();
} catch (RuntimeException | Error e) {
  span.error(e); // Unless you handle exceptions, you might not know the operation failed!
  throw e;
} finally {
  span.finish(); // note the scope is independent of the span. Always finish a span.
}

以上两个示例都报告了完成时完全相同的 span!

在上面的示例中,span 将是新的根 span 或现有跟踪中的下一个子节点。

50.1.3 自定义 Spans

一旦有了 span,就可以为它添加标签。标签可用作查找键或详细信息。对于 example,您可以使用运行时 version 添加标记,如以下 example 所示:

span.tag("clnt/finagle.version", "6.36.0");

在向第三方公开自定义 spans 的能力时,更喜欢brave.SpanCustomizer而不是brave.Span。前者更容易理解和测试,并且不会使用 span 生命周期钩子诱惑用户。

interface MyTraceCallback {
  void request(Request request, SpanCustomizer customizer);
}

由于brave.Span实现brave.SpanCustomizer,您可以将其传递给用户,如下面的示例所示:

for (MyTraceCallback callback : userCallbacks) {
  callback.request(request, span);
}

50.1.4 隐含地查看当前的 Span

有时,您不知道跟踪是否正在进行,并且您不希望用户执行空检查。 brave.CurrentSpanCustomizer通过将数据添加到正在进行或删除的任何 span 来处理此问题,如下面的示例所示:

防爆。

// The user code can then inject this without a chance of it being null.
@Autowired SpanCustomizer span;

void userCode() {
  span.annotate("tx.started");
  ...
}

50.1.5 RPC 跟踪

在滚动自己的 RPC 检测之前检查这里写的仪器Zipkin 的名单

RPC 跟踪通常由拦截器自动完成。在幕后,他们添加了与他们在 RPC 操作中的角色相关的标签和事件。

以下 example 显示了如何添加 client span:

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// before you send a request, add metadata that describes the operation
span = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);
span.tag("myrpc.version", "1.0.0");
span.remoteServiceName("backend");
span.remoteIpAndPort("172.3.4.1", 8108);

// Add the trace context to the request, so it can be propagated in-band
tracing.propagation().injector(Request::addHeader)
                     .inject(span.context(), request);

// when the request is scheduled, start the span
span.start();

// if there is an error, tag the span
span.tag("error", error.getCode());
// or if there is an exception
span.error(exception);

// when the response is complete, finish the span
span.finish();

One-Way 追踪

有时,您需要 model 一个异步操作,其中有一个请求但没有响应。在正常的 RPC 跟踪中,使用span.finish()表示已收到响应。在 one-way 跟踪中,您使用span.flush()代替,因为您不期望响应。

以下 example 显示了 client 如何 model one-way 操作:

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// start a new span representing a client request
oneWaySend = tracer.nextSpan().name(service + "/" + method).kind(CLIENT);

// Add the trace context to the request, so it can be propagated in-band
tracing.propagation().injector(Request::addHeader)
                     .inject(oneWaySend.context(), request);

// fire off the request asynchronously, totally dropping any response
request.execute();

// start the client side and flush instead of finish
oneWaySend.start().flush();

以下 example 显示了服务器如何处理 one-way 操作:

@Autowired Tracing tracing;
@Autowired Tracer tracer;

// pull the context out of the incoming request
extractor = tracing.propagation().extractor(Request::getHeader);

// convert that context to a span which you can name and add tags to
oneWayReceive = nextSpan(tracer, extractor.extract(request))
    .name("process-request")
    .kind(SERVER)
    ... add tags etc.

// start the server side and flush instead of finish
oneWayReceive.start().flush();

// you should not modify this span anymore as it is complete. However,
// you can create children to represent follow-up work.
next = tracer.newSpan(oneWayReceive.context()).name("step2").start();