使用 Spring Cloud Sleuth

1. 使用 Spring Cloud Sleuth 的 API 跨越生命周期

Spring Cloud Sleuth Core 在其api模块包含跟踪器要实现的所有必要接口。 该项目附带了 OpenZipkin Brave 实现。 您可以通过查看org.springframework.cloud.sleuth.brave.bridge.spring-doc.cadn.net.cn

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

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

  • org.springframework.cloud.sleuth.Span- 跨度是需要启动和停止的单个工作单元。 包含计时信息以及事件和标记。spring-doc.cadn.net.cn

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

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

Spring Cloud Sleuth 创建了一个Tracer给你的。 为了使用它,您可以自动连接它。

1.1. 创建和结束跨度

您可以使用Tracer,如以下示例所示: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();
}

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

创建跨度后始终进行清洁。
如果 span 包含的名称大于 50 个字符,则该名称将截断为 50 个字符。 你的名字必须明确具体。 大牌会导致延迟问题,有时甚至会导致异常。

1.2. 连续跨度

有时,您不想创建新的跨度,但又想继续创建一个跨度。 这种情况的一个示例可能如下:spring-doc.cadn.net.cn

  • AOP:如果在达到某个方面之前已经创建了一个跨度,您可能不想创建新的跨度。spring-doc.cadn.net.cn

要继续一个 span,您可以将 span 存储在一个线程中,并将其传递给另一个线程,如下面的示例所示。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. 创建具有显式父级的跨度

您可能希望启动一个新跨度并提供该跨度的显式父级。 假设一个 span 的父级位于一个线程中,并且您想在另一个线程中启动一个新 span。 每当您致电Tracer.nextSpan(),它会创建一个 span 以引用当前作用域内的 span。 可以将 span 放入作用域中,然后调用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();
    }
}
创建这样的跨度后,您必须完成它。 否则不会报告(例如,向 Zipkin)。

您还可以使用Tracer.nextSpan(Span parentSpan)version 来显式提供父 Span。spring-doc.cadn.net.cn

2. 命名跨度

选择跨度名称不是一项微不足道的任务。 span 名称应描述作名称。 名称应为低基数,因此不应包含标识符。spring-doc.cadn.net.cn

由于有很多仪器正在进行,因此一些跨度名称是人为的: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 将命名为calculateTax:spring-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,如以下示例所示: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 无关的意味着与跨度协作。 使用注释允许用户添加到对 span api 没有库依赖关系的 span。 这样做可以让 Sleuth 更改其核心 API,以减少对用户代码的影响。spring-doc.cadn.net.cn

  • 减少基本跨度作的表面积。 如果没有此功能,则必须使用 span api,该 API 具有可能使用不正确的生命周期命令。 通过仅公开范围、标记和日志功能,您可以进行协作而不会意外中断跨度生命周期。spring-doc.cadn.net.cn

  • 与运行时生成的代码协作。 使用 Spring Data 和 Feign 等库,接口的实现是在运行时生成的。 因此,对象的跨度包装很乏味。 现在,您可以为接口和这些接口的参数提供注释。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参数),创建的 span 具有提供的值作为名称。spring-doc.cadn.net.cn

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

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

您可以组合名称和标签。 让我们关注后者。 在这种情况下,带注释的方法的参数运行时值的值将成为标记的值。 在我们的示例中,标记键是testTag,标签值为test.spring-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();

(请注意,与@NewSpan注释,您还可以使用log参数。spring-doc.cadn.net.cn

这样,跨度就会继续下去,并且:spring-doc.cadn.net.cn

3.3. 高级标签设置

有 3 种不同的方法可以向跨度添加标签。 所有这些都由SpanTag注解。 优先顺序如下:spring-doc.cadn.net.cn

  1. 尝试用TagValueResolvertype 和提供的名称。spring-doc.cadn.net.cn

  2. 如果尚未提供 bean 名称,请尝试计算表达式。 我们搜索一个TagValueExpressionResolver豆。 缺省实现使用 SPEL 表达式解析。重要只能从 SPEL 表达式中引用属性。 由于安全限制,不允许执行方法。spring-doc.cadn.net.cn

  3. 如果我们没有找到任何要计算的表达式,则返回toString()参数的值。spring-doc.cadn.net.cn

3.3.1. 自定义提取器

以下方法的标记值由TagValueResolver接口。 它的类名必须作为resolver属性。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 myCustomTagValueResolver.spring-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 表达式的评估,并且值为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导致设置 String 值为"15".spring-doc.cadn.net.cn

4. 接下来要读什么

您现在应该了解如何使用 Spring Cloud Sleuth 以及您应该遵循的一些最佳实践。 您现在可以继续了解特定的 Spring Cloud Sleuth 功能,或者您可以跳过并阅读有关 Spring Cloud Sleuth 中可用的集成spring-doc.cadn.net.cn