使用 Spring Cloud Sleuth
1. 使用 Spring Cloud Sleuth 的 API 跨度生命周期
Spring Cloud Sleuth Core 在其模块中包含要由跟踪器实现的所有必要接口。
该项目附带 OpenZipkin Brave 实现。
您可以通过查看 .api
org.springframework.cloud.sleuth.brave.bridge
最常用的接口是:
-
org.springframework.cloud.sleuth.Tracer
- 使用跟踪器,您可以创建捕获请求关键路径的根 span。 -
org.springframework.cloud.sleuth.Span
- Span 是需要启动和停止的单个工作单元。 包含计时信息以及事件和标签。
您还可以直接使用 tracer 实现的 API。
让我们看看以下 Span 生命周期操作。
-
start:启动 span 时,将分配其名称并记录开始时间戳。
-
end:span 完成(记录 span 的结束时间),如果 span 被采样,则它有资格进行收集(例如到 Zipkin)。
-
continue:span 被继续,例如在另一个线程中。
-
create with explicit parent(使用显式父级创建):您可以创建一个新的 span 并为其设置显式父级。
Spring Cloud Sleuth 为您创建一个实例。
为了使用它,您可以自动装配它。Tracer |
1.1. 创建和结束 Span
您可以使用 手动创建 Span,如以下示例所示:Tracer
// 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,则它将成为新 span 的父级。
创建 Span 后始终保持干净。 |
如果您的 span 包含大于 50 个字符的名称,则该名称将被截断为 50 个字符。 你的名字必须明确而具体。 大牌会导致延迟问题,有时甚至会导致异常。 |
1.2. 连续跨度
有时,您不想创建新的 span,但想要继续创建一个新的 span。 这种情况的一个示例可能如下:
-
AOP:如果在到达 aspect 之前已经创建了一个 span,则您可能不想创建新的 span。
要继续 span,您可以将 span 存储在一个线程中,并将其传递给另一个线程,如以下示例所示。
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. 创建具有显式 Parent 的 Span
您可能希望启动一个新的 span 并提供该 span 的显式父级。
假设 span 的父级位于一个线程中,并且您希望在另一个线程中启动新的 span。
每当调用 时,它都会创建一个引用当前范围内的 span 的 span。
您可以将 span 放入 scope,然后调用 ,如以下示例所示:Tracer.nextSpan()
Tracer.nextSpan()
// 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();
}
}
创建此类 span 后,必须完成它。 否则不会报告(例如向 Zipkin)。 |
您还可以使用 version 显式提供父 span。Tracer.nextSpan(Span parentSpan)
2. 命名 Span
选择 span 名称并非易事。 范围名称应描述操作名称。 名称应为低基数,因此不应包含标识符。
由于正在进行大量插桩,因此一些 span 名称是人为的:
-
controller-method-name
当被方法名称为controllerMethodName
-
async
对于使用 wrapped 和 interfaces 完成的异步操作。Callable
Runnable
-
带 Comments 的方法返回类的简单名称。
@Scheduled
幸运的是,对于异步处理,您可以提供显式命名。
2.1. @SpanName
注解
您可以使用注释显式命名 span,如以下示例所示:@SpanName
@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {
@Override
public void run() {
// perform logic
}
}
在这种情况下,当按以下方式处理时,span 将被命名为 :calculateTax
Runnable runnable = new TraceRunnable(this.tracer, spanNamer, new TaxCountingRunnable());
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();
2.2. toString()
方法
为 或 创建单独的类是非常罕见的。
通常,可以创建这些类的匿名实例。
您不能注释此类。
为了克服该限制,如果不存在 Comments,我们将检查该类是否具有该方法的自定义实现。Runnable
Callable
@SpanName
toString()
运行此类代码会导致创建一个名为 的 span,如以下示例所示:calculateTax
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. 使用 Comments 管理 Span
使用 Comments 管理 span 有很多充分的理由,包括:
-
API 不可知的意思是与 span 协作。 使用注释允许用户添加到 span,而无需依赖于 span api。 这样做可以让 Sleuth 更改其核心 API,以减少对用户代码的影响。
-
减少了基本跨度操作的表面积。 如果没有此功能,您必须使用 span api,该 api 包含可能被错误使用的生命周期命令。 通过仅公开范围、标签和日志功能,您可以进行协作,而不会意外中断 span 生命周期。
-
与运行时生成的代码协作。 使用 Spring Data 和 Feign 等库,接口的实现是在运行时生成的。 因此,对象的 span 包装很繁琐。 现在,你可以通过接口和这些接口的参数提供注释。
3.1. 创建新的 Span
如果您不想手动创建本地 span,则可以使用 annotation。
此外,我们还提供了 annotation 以自动方式添加标签。@NewSpan
@SpanTag
现在我们可以考虑一些用法示例。
@NewSpan
void testMethod();
在没有任何参数的情况下对方法进行注释会导致创建一个名称等于带注释的方法名称的新 span。
@NewSpan("customNameOnTestMethod4")
void testMethod4();
如果在注释中提供值(直接或通过设置参数),则创建的 span 将提供的值作为名称。name
// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);
// and method execution
this.testBean.testMethod5("test");
您可以组合名称和标签。
让我们专注于后者。
在这种情况下,带注释方法的参数 runtime value 的值将成为 tag 的值。
在我们的示例中,标签键为 ,标签值为 。testTag
test
@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}
您可以将 Comments 放在类和接口上。
如果覆盖接口的方法并为 annotation 提供不同的值,则最具体的值优先(在本例中为 set)。@NewSpan
@NewSpan
customNameOnTestMethod3
3.2. 连续 Span
如果要向现有范围添加标记和注释,可以使用注释,如以下示例所示:@ContinueSpan
// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);
// method execution
this.testBean.testMethod11("test");
this.testBean.testMethod13();
(请注意,与 annotation 相反,您还可以使用 parameter 添加日志。@NewSpan
log
这样,跨度就会继续,并且:
-
日志条目 named 和 created。
testMethod11.before
testMethod11.after
-
如果引发异常,还会创建一个名为
testMethod11.afterFailure
-
将创建键为 和 值为 的标签。
testTag11
test
3.3. 高级标签设置
有 3 种不同的方法可以向 Span 添加标签。
它们都由 Annotation 控制。
优先级如下:SpanTag
-
尝试使用 type 和 provided name 的 bean。
TagValueResolver
-
如果未提供 bean 名称,请尝试计算表达式。 我们搜索 bean。 默认实现使用 SPEL 表达式解析。重要您只能从 SPEL 表达式中引用属性。 由于安全约束,不允许执行方法。
TagValueExpressionResolver
-
如果找不到任何要计算的表达式,则返回参数的值。
toString()
3.3.1. 自定义提取器
以下方法的 tag 值由 interface 的实现计算。
它的类名必须作为属性的值传递。TagValueResolver
resolver
请考虑以下带注释的方法:
@NewSpan
public void getAnnotationForTagValueResolver(
@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}
现在进一步考虑以下 bean 实现:TagValueResolver
@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
return parameter -> "Value from myCustomTagValueResolver";
}
前面的两个示例导致将 tag 值设置为等于 。Value from myCustomTagValueResolver
3.3.2. 解析值的表达式
请考虑以下带注释的方法:
@NewSpan
public void getAnnotationForTagValueExpression(
@SpanTag(key = "test", expression = "'hello' + ' characters'") String test) {
}
没有自定义 a 的实现会导致对 SPEL 表达式的计算,并且在 span 上设置了值为 的标记。
如果要使用其他表达式解析机制,则可以创建自己的 bean 实现。TagValueExpressionResolver
4 characters
3.3.3. 使用 toString()
方法
请考虑以下带注释的方法:
@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}
运行值为 的上述方法会导致设置 String 值为 .15
"15"
4. 下一步阅读什么
您现在应该了解如何使用 Spring Cloud Sleuth 以及您应该遵循的一些最佳实践。 您现在可以继续了解特定的 Spring Cloud Sleuth 功能,也可以跳过并阅读 Spring Cloud Sleuth 中提供的集成。