Span生命周期
您可以通过org.springframework.cloud.sleuth.Tracer接口在Span上执行以下操作:
-
开始 - 当您启动一个span时,它的名称被分配,并且记录开始时间戳。
-
关闭 - 跨度完成(记录跨度的结束时间),如果跨度可导出,则它将有资格收集到Zipkin。该跨度也从当前线程中移除。
-
继续 - 将创建一个新的跨度实例,而它将是它继续的一个副本。
-
分离 - 跨度不会停止或关闭。它只从当前线程中删除。
-
使用显式父项创建 - 您可以创建一个新的跨度,并为其设置一个显式父级
提示 | Spring为您创建了一个Tracer的实例。为了使用它,你需要的只是自动连接它。 |
---|
创建和关闭spans
您可以使用Tracer界面手动创建spans 。
// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.createSpan("calculateTax");
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
newSpan.logEvent("taxCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin
this.tracer.close(newSpan);
}
在这个例子中,我们可以看到如何创建一个新的跨度实例。假设这个线程中已经存在跨度,那么它将成为该跨度的父代。
重要 | 创建跨度后始终清洁!如果要将其发送到Zipkin,请不要忘记关闭跨度。 |
---|
重要 | 如果您的span包含的名称大于50个字符,则该名称将被截断为50个字符。你的名字必须是明确而具体的。大名称导致延迟问题,有时甚至引发异常。 |
---|
继续spans
有时你不想创建一个新的跨度,但你想继续。这种情况的例子可能是(当然这取决于用例):
-
AOP - 如果在达到方面之前已经创建了一个跨度,则可能不想创建一个新的跨度。
-
Hystrix - 执行Hystrix命令很可能是当前处理的逻辑部分。实际上,它只是一个技术实现细节,你不一定要反映在跟踪中作为一个单独的存在。
扫描二维码关注公众号,回复: 4124469 查看本文章
持续的跨度实例等于它继续的范围:
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
assertThat(continuedSpan).isEqualTo(spanToContinue);
要继续跨度,您可以使用Tracer界面。
// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("taxValue", taxValue);
// ...
// You can log an event on a span
continuedSpan.logEvent("taxCalculated");
} finally {
// Once done remember to detach the span. That way you'll
// safely remove it from the current thread without closing it
this.tracer.detach(continuedSpan);
}
重要 | 创建跨度后始终清洁!如果在一个线程(例如线程X)中开始了某些工作,并且正在等待其他线程(例如Y,Z)完成,请不要忘记分离跨距。那么线程Y,Z中的spans在工作结束时应该被分离。当收集结果时,螺纹X中的跨度应该被关闭。 |
---|
用明确的父代创建spans
您可能想要开始一个新的跨度,并提供该跨度的显式父级。假设跨度的父项在一个线程中,并且要在另一个线程中启动一个新的跨度。Tracer接口的startSpan方法是您要查找的方法。
// 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 = this.tracer.createSpan("calculateCommission", initialSpan);
try {
// ...
// You can tag a span
this.tracer.addTag("commissionValue", commissionValue);
// ...
// You can log an event on a span
newSpan.logEvent("commissionCalculated");
} finally {
// Once done remember to close the span. This will allow collecting
// the span to send it to Zipkin. The tags and events set on the
// newSpan will not be present on the parent
this.tracer.close(newSpan);
}
重要 | 创建这样一个跨度后,记得关闭它。否则,您将在您的日志中看到很多警告,其中有一个事实,即您在当前线程中存在一个跨度,而不是您要关闭的线程。更糟糕的是,您的spans不会正确关闭,因此不会收集到Zipkin。 |
---|
命名spans
选择一个跨度名称不是一件小事。Span名称应该描述一个操作名称。名称应该是低基数(例如不包括标识符)。
由于有很多仪器仪表在一些跨度名称将是人为的:
-
controller-method-name当控制器以方法名conrollerMethodName接收时
-
async通过包装Callable和Runnable完成异步操作。
-
@Scheduled注释方法将返回类的简单名称。
幸运的是,对于异步处理,您可以提供明确的命名。
@SpanName注释
您可以通过@SpanName注释显式指定该跨度。
@SpanName("calculateTax")
class TaxCountingRunnable implements Runnable {
@Override public void run() {
// perform logic
}
}
在这种情况下,以下列方式处理时:
Runnable runnable = new TraceRunnable(tracer, spanNamer, new TaxCountingRunnable());
Future<?> future = executorService.submit(runnable);
// ... some additional logic ...
future.get();
该范围将被命名为calculateTax。
toString()方法
为Runnable或Callable创建单独的课程很少见。通常,创建这些类的匿名实例。如果没有@SpanName注释,我们将检查该类是否具有toString()方法的自定义实现。
所以执行这样的代码:
Runnable runnable = new TraceRunnable(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();
将导致创建一个名为calculateTax的跨度。
管理spans注释
合理
这个功能的主要论据是
- api-agnostic意味着与跨度进行合作
- 使用注释允许用户添加到跨度api没有库依赖的跨度。这允许Sleuth将其核心api的影响改变为对用户代码的影响较小。
- 减少基础跨度作业的表面积。
- 没有这个功能,必须使用span api,它具有不正确使用的生命周期命令。通过仅显示范围,标签和日志功能,用户可以协作,而不会意外中断跨度生命周期。
- 与运行时生成的代码协作
- 使用诸如Spring Data / Feign的库,在运行时生成接口的实现,从而跨越对象的包装是乏味的。现在,您可以通过这些接口的接口和参数提供注释
创建新的spans
如果您真的不想手动创建本地spans,您可以从@NewSpan注释中获利。此外,我们还提供@SpanTag注释,以自动方式添加标签。
我们来看一些使用的例子。
@NewSpan
void testMethod();
注释没有任何参数的方法将导致创建名称将等于注释方法名称的新跨度。
@NewSpan("customNameOnTestMethod4")
void testMethod4();
如果您在注释中提供值(直接或通过name参数),则创建的范围将具有提供的值的名称。
// method declaration
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);
// and method execution
this.testBean.testMethod5("test");
您可以组合名称和标签。我们来关注后者。在这种情况下,无论注释方法的参数运行时值的值如何 - 这将是标记的值。在我们的示例中,标签密钥将为testTag,标签值为test。
@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}
您可以将@NewSpan注释放在类和接口上。如果覆盖接口的方法并提供不同的@NewSpan注释值,则最具体的一个获胜(在这种情况下customNameOnTestMethod3将被设置)。
继续spans
如果您只想添加标签和注释到现有的跨度,就可以使用如下所示的@ContinueSpan注释。请注意,与@NewSpan注释相反,您还可以通过log参数添加日志:
// method declaration
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);
// method execution
this.testBean.testMethod11("test");
这样,跨越将继续下去:
-
将创建名称为testMethod11.before和testMethod11.after的日志
-
如果抛出异常,也将创建一个日志testMethod11.afterFailure
-
将创建密钥testTag11和值test的标签
更高级的标签设置
有三种不同的方法可以将标签添加到跨度。所有这些都由SpanTag注释控制。优先级是:
-
尝试使用TagValueResolver类型的bean,并提供名称
-
如果没有提供bean名称,请尝试评估一个表达式。我们正在搜索一个TagValueExpressionResolver bean。默认实现使用SPEL表达式解析。
-
如果没有提供任何表达式来评估只返回参数的toString()值
自定义提取器
以下方法的标签值将由TagValueResolver接口的实现来计算。其类名必须作为resolver属性的值传递。
有这样一个注释的方法:
@NewSpan
public void getAnnotationForTagValueResolver(@SpanTag(key = "test", resolver = TagValueResolver.class) String test) {
}
和这样一个TagValueResolver bean实现
@Bean(name = "myCustomTagValueResolver")
public TagValueResolver tagValueResolver() {
return parameter -> "Value from myCustomTagValueResolver";
}
将导致标签值的设置等于Value from myCustomTagValueResolver。
解决表达式的价值
有这样一个注释的方法:
@NewSpan
public void getAnnotationForTagValueExpression(@SpanTag(key = "test", expression = "length() + ' characters'") String test) {
}
并且没有自定义的TagValueExpressionResolver实现将导致对SPEL表达式的评估,并且将在span上设置值为4 characters的标签。如果要使用其他表达式解析机制,您可以创建自己的bean实现。
使用toString方法
有这样一个注释的方法:
@NewSpan
public void getAnnotationForArgumentToString(@SpanTag("test") Long param) {
}
如果使用值为15执行,则将导致设置String值为"15"的标记。
欢迎关注作者的公众号《Java编程生活》,每日记载Java程序猿工作中遇到的问题