52. Features

  • 将跟踪和 SpanID 添加到 Slf4J MDC,因此您可以在日志聚合器中从给定的跟踪或 Span 提取所有日志,如以下示例日志所示:
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 的应用程序的名称。

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

    • exportable :是否应将日志导出到 Zipkin。您何时希望 Span 不可导出?当您要将某些操作包装在 Span 中并将其仅写入日志时。

  • 提供对常见的分布式跟踪数据模型的抽象:跟踪,Span(形成 DAG),Comments 和键值 Comments。 Spring Cloud Sleuth 宽松地基于 HTrace,但与 Zipkin(Dapper)兼容。

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

  • 编写 Sleuth 时不要过多记录日志,也不会导致生产应用程序崩溃。为此,Sleuth:

  • 在带内传播有关调用图的结构数据,并在带外传播其余数据。

    • 包括对诸如 HTTP 之类的层的自觉检测。

    • 包括用于 Management 数量的采样策略。

    • 可以报告给 Zipkin 系统进行查询和可视化。

  • 从 Spring 应用程序(Servlet filter,异步端点,Rest 模板,计划的动作,消息通道,Zuul filter和 FeignClient 端)检测常见的入口和 Export 点。

  • Sleuth 包含默认逻辑以跨 HTTP 或消息传递边界加入跟踪。例如,HTTP 传播在与 Zipkin 兼容的请求 Headers 上工作。

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

  • 提供一种创建或 continueSpan 以及通过 Comments 添加标签和日志的方法。

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

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

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

Warning

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

Tip

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

Note

始终设置 SLF4J MDC,并且按先前显示的示例,登录用户可以立即在日志中看到跟踪和 SpanID。其他日志记录系统必须配置自己的格式化程序才能获得相同的结果。默认值如下: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 功能)。如果您不使用 SLF4J,则不会自动应用此模式。

52.1Brave 简介

Tip

从版本2.0.0开始,Spring Cloud Sleuth 使用Brave作为跟踪库。为方便起见,我们在此处嵌入了 Brave 文档的一部分。

Tip

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

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

该模块包括一个跟踪器,该跟踪器创建并连接 Span,以对潜在的分布式工作的延迟进行建模。它还包括用于在网络边界上传播跟踪上下文的库(例如,使用 HTTPHeaders)。

52.1.1 Tracing

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

以下示例安装程序通过 HTTP(与 Kafka 相对)将跟踪数据(Span)发送到 Zipkin:

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();
        // ...
    }
}

Tip

如果您的 Span 包含的名称长于 50 个字符,则该名称将被截断为 50 个字符。您的姓名必须明确明确。知名人士会导致延迟问题,有时甚至会引发异常。

跟踪器创建并连接 Span,以对潜在的分布式工作的延迟进行建模。它可以采用采样来减少处理过程中的开销,减少发送到 Zipkin 的数据量或两者。

跟踪器返回的跨距在完成后将数据报告给 Zipkin,如果未采样则不执行任何操作。开始 Span 后,您可以 Comments 感兴趣的事件或添加包含详细信息或查找键的标签。

Span 具有包括跟踪标识符的上下文,该标识符将 Span 放置在代表分布式操作的树中的正确位置。

52.1.2 本地跟踪

跟踪永远不会离开进程的代码时,请在范围范围内运行它。

@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
}

当您需要更多功能或更好的控制时,请使用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.
}

上面的两个示例报告的结束时间完全相同!

在上面的示例中,范围将是新的根范围或现有跟踪中的下一个子级。

52.1.3 自定义 Span

一旦具有 Span,就可以向其添加标签。标记可用作查找键或详细信息。例如,您可以在运行时版本中添加标签,如以下示例所示:

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

向第三方公开自定义 Span 的功能时,最好使用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);
}

52.1.4 隐式查找当前 Span

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

Ex.

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

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

52.1.5 RPC 跟踪

Tip

滚动自己的 RPC 工具之前,请检查这里写的仪器Zipkin's list

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

以下示例显示了如何添加 Client 端范围:

@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 tracing

有时,您需要对有请求但无响应的异步操作进行建模。在普通的 RPC 跟踪中,您使用span.finish()表示已收到响应。在单向跟踪中,您使用span.flush()代替,因为您不希望得到响应。

下面的示例说明 Client 端如何建模单向操作:

@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();

下面的示例显示服务器如何处理单向操作:

@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();