50. Features

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]条目:

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();
首页