使用Spring CloudSleuth

1. Span Lifecycle with Spring Cloud Sleuth's API

Spring Cloud Sleuth Core 在其api模块中包含所有必要的接口,必须由跟踪程序实现。该项目随OpenZipkin Brave 实现一起提供。您可以查看org.springframework.cloud.sleuth.brave.bridge如何通过查看Sleuth的API来桥接跟踪程序,以了解如何通过查看Sleuth的API来桥接跟踪程序。spring-doc.cadn.net.cn

最常用的功能接口是:spring-doc.cadn.net.cn

  • org.springframework.cloud.sleuth.Tracer - 使用追踪器,您可以创建一个根跨度,捕获请求的关键路径。spring-doc.cadn.net.cn

  • 0 - Span 是需要启动和停止的工作单元。包含时间信息、事件和标签。spring-doc.cadn.net.cn

你也可以直接使用你所实现的追踪器API。spring-doc.cadn.net.cn

让我们看看以下跨度生命周期操作。spring-doc.cadn.net.cn

Spring Cloud Sleuth为你创建了一个Tracer实例。

spring-doc.cadn.net.cn

为了使用它,你可以自动注入它。spring-doc.cadn.net.cn

1.1. 创建和结束跨度

你可以通过使用 Tracer 手动创建 spans,如下例所示:spring-doc.cadn.net.cn

// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.nextSpan().name("calculateTax");
try (Tracer.SpanInScope ws = this.tracer.withSpan(newSpan.start())) {
    // ...
    // You can tag a span
    newSpan.tag("taxValue", taxValue);
    // ...
    // You can log an event on a span
    newSpan.event("taxCalculated");
}
finally {
    // Once done remember to end the span. This will allow collecting
    // the span to send it to a distributed tracing system e.g. Zipkin
    newSpan.end();
}

在前面的示例中,我们可以看到如何创建一个新的跨度。如果线程中已经有一个跨度,则该跨度成为新跨度的父级。spring-doc.cadn.net.cn

始终在创建 span 后清理。
如果你的跨度包含一个长度超过 50 个字符的名称,那么该名称将被截断为 50 个字符。 您的名称必须明确且具体。 过长的名称会导致延迟问题,甚至有时会出现异常。

1.2. 继续跨度

有时,您不希望创建一个新的span,而是希望继续使用一个现有的。<br/>这种情况的一个例子可能如下所示:spring-doc.cadn.net.cn

  • AOP: 如果在到达切面之前已经创建了 span,你可能不想创建新的 span。spring-doc.cadn.net.cn

要继续一个跨度,您可以在线程中存储该跨度,并将其传递到另一个线程,如下例所示。spring-doc.cadn.net.cn

Span spanFromThreadX = this.tracer.nextSpan().name("calculateTax");
try (Tracer.SpanInScope ws = this.tracer.withSpan(spanFromThreadX.start())) {
    executorService.submit(() -> {
        // Pass the span from thread X
        Span continuedSpan = spanFromThreadX;
        // ...
        // You can tag a span
        continuedSpan.tag("taxValue", taxValue);
        // ...
        // You can log an event on a span
        continuedSpan.event("taxCalculated");
    }).get();
}
finally {
    spanFromThreadX.end();
}

1.3. 创建具有显式父级的跨度

您可能需要启动一个新的跨度并提供该跨度的显式父级。
假设一个跨度的父级位于一个线程中,而您希望在另一个线程中启动一个新的跨度。
每当调用 Tracer.nextSpan()时,它会创建相对于当前作用域中的跨度的新跨度。
您可以将跨度放入作用域中,然后调用 Tracer.nextSpan(),如下面的示例所示:spring-doc.cadn.net.cn

// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = null;
try (Tracer.SpanInScope ws = this.tracer.withSpan(initialSpan)) {
    newSpan = this.tracer.nextSpan().name("calculateCommission");
    // ...
    // You can tag a span
    newSpan.tag("commissionValue", commissionValue);
    // ...
    // You can log an event on a span
    newSpan.event("commissionCalculated");
}
finally {
    // Once done remember to end the span. This will allow collecting
    // the span to send it to e.g. Zipkin. The tags and events set on the
    // newSpan will not be present on the parent
    if (newSpan != null) {
        newSpan.end();
    }
}
创建此类跨度后,您必须将其结束。</p><p>否则不会报告(例如到 Zipkin)。

您也可以使用 Tracer.nextSpan(Span parentSpan) 版本显式提供父跨度。spring-doc.cadn.net.cn

2. 命名跨度

选择跨度名称并不是一项简单的任务。<br/>一个跨度名称应该表示操作名称。<br/>该名称的基数应较低,因此不应包含标识符。spring-doc.cadn.net.cn

Since there is a lot of instrumentation going on, some span names are artificial:spring-doc.cadn.net.cn

幸运的是,对于异步处理,您可以提供显式命名。spring-doc.cadn.net.cn

2.1. @SpanName 注解

您可以使用注释@SpanName显式命名跨度,如以下示例所示:spring-doc.cadn.net.cn

@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {

    @Override
    public void run() {
        // perform logic
    }

}

在本例中,以以下方式处理时,span 被命名为calculateTaxspring-doc.cadn.net.cn

Runnable runnable = new TraceRunnable(this.tracer, spanNamer, new TaxCountingRunnable());
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();

2.2. toString() 方法

很少单独创建RunnableCallable类。通常,会创建这些类的匿名实例。无法为此类添加注解。如果没有@SpanName注解,我们会检查类是否具有toString()方法的自定义实现。spring-doc.cadn.net.cn

运行此代码会导致创建一个名为calculateTax的span,如下面的示例所示:spring-doc.cadn.net.cn

Runnable runnable = new TraceRunnable(this.tracer, spanNamer, new Runnable() {
    @Override
    public void run() {
        // perform logic
    }

    @Override
    public String toString() {
        return "calculateTax";
    }
});
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();

3. 使用注解管理跨度

使用注解管理跨度有诸多优点,包括:spring-doc.cadn.net.cn

  • 与跨度协作的无 API 依赖方法。<br>使用注解让用户在不依赖于跨度 API 库的情况下向跨度添加内容。<br>这样,Sleuth 可以更改其核心 API,对用户代码的影响更小。spring-doc.cadn.net.cn

  • 减少了基本跨度操作的表面积。 如果没有此功能,您必须使用跨度API,该API具有生命周期命令,可能会被错误地使用。 通过仅暴露范围、标记和日志功能,您可以协作而不会意外中断跨度生命周期。spring-doc.cadn.net.cn

  • 与运行时生成的代码协作。<br>使用 Spring Data 和 Feign 等库,接口的实现是在运行时生成的。<br>因此,对象的跨度包装很繁琐。<br>现在您可以在接口及其参数上提供注解。spring-doc.cadn.net.cn

3.1. 创建新跨度

如果不想手动创建本地跨度,可以使用@NewSpan注释。 另外,我们提供了@SpanTag注释,可以自动添加标记。spring-doc.cadn.net.cn

现在我们可以考虑一些用法示例。spring-doc.cadn.net.cn

@NewSpan
void testMethod();

使用没有任何参数的方法注释可创建新的跨度,其名称等于注释方法名。spring-doc.cadn.net.cn

@NewSpan("customNameOnTestMethod4")
void testMethod4();

如果您在注释中提供值(直接或通过设置name参数),则创建的跨度具有提供的值作为名称。spring-doc.cadn.net.cn

// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);

// and method execution
this.testBean.testMethod5("test");

您可以将名称和标记结合起来。让我们专注于后者。在这种情况下,注解方法参数的运行时值成为标记的值。在我们的示例中,标记键是testTag,标记值是testspring-doc.cadn.net.cn

@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}

你可以将 @NewSpan 注解放置在类和接口上。
如果你重写了接口的方法并为 @NewSpan 注解提供了不同的值,那么最具体的注解会胜出(在这种情况下设置为 customNameOnTestMethod3)。spring-doc.cadn.net.cn

3.2. 继续跨度

如果您想为现有跨度添加标签和注释,可以使用@ContinueSpan注释,如以下示例所示:spring-doc.cadn.net.cn

// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);

// method execution
this.testBean.testMethod11("test");
this.testBean.testMethod13();

(练传时,可以通过接发“120”参数,导入日志)spring-doc.cadn.net.cn

That way, the span gets continued and:spring-doc.cadn.net.cn

3.3 高级标签设置

有三种不同的方式可以向跨度添加标签。它们都由SpanTag注解控制。优先级如下:spring-doc.cadn.net.cn

  1. 使用类型为TagValueResolver且提供了名称的Bean尝试一下。spring-doc.cadn.net.cn

  2. 如果未提供bean名称,请尝试评估表达式。
    我们搜索TagValueExpressionResolver bean。
    默认实现使用SPEL表达式解析。
    重要您只能从SPEL表达式中引用属性。
    由于安全约束,不允许执行方法。spring-doc.cadn.net.cn

  3. 如果找不到要计算的表达式,那么就返回参数的toString()值。spring-doc.cadn.net.cn

3.3.1. 自定义提取器

The value of the tag for the following method is computed by an implementation of TagValueResolver interface. Its class name has to be passed as the value of the resolver attribute.spring-doc.cadn.net.cn

请考虑以下注释方法:spring-doc.cadn.net.cn

@NewSpan
public void getAnnotationForTagValueResolver(
        @SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}

现在进一步考虑以下TagValueResolverbean实现:spring-doc.cadn.net.cn

@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
    return parameter -> "Value from myCustomTagValueResolver";
}

前面的两个示例会导致设置一个标签值等于 Value from myCustomTagValueResolverspring-doc.cadn.net.cn

3.3.2. 解析值的表达式

请考虑以下注释方法:spring-doc.cadn.net.cn

@NewSpan
public void getAnnotationForTagValueExpression(
        @SpanTag(key = "test", expression = "'hello' + ' characters'") String test) {
}

如果没有对TagValueExpressionResolver提供自定义实现,则会评估SPEL表达式,且在span上将值为4 characters的标记被设置。 如果你想要使用其他表达式解析机制,可以创建你自己的bean实现。spring-doc.cadn.net.cn

3.3.3 使用toString()方法

请考虑以下注释方法:spring-doc.cadn.net.cn

@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}

使用值为 15 的前一方法将设置一个值为 "15" 的标签属性。spring-doc.cadn.net.cn

4. 接下来阅读什么

现在,您应该了解如何使用 Spring Cloud Sleuth 以及应遵循的一些最佳实践。
接下来,您可以学习有关特定的
Spring Cloud Sleuth 功能,或者跳到前面阅读关于 Spring Cloud Sleuth 中可用的集成spring-doc.cadn.net.cn