开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
分布式跟踪解决方案。
Spring Cloud Sleuth能够跟踪您的请求和消息,以便您能够将通信与相应的日志条目关联起来。
您还可以将跟踪信息导出到外部系统,以可视化延迟。Spring Cloud Sleuth直接支持OpenZipkin兼容的系统。
术语
- Span:基本工作单元,发送一个请求和发送一个响应,都是一个span,每个span还包括
descriptions,timestamped events,key-value annotations (tags)、span id和进程id(通常是IP地址)。一旦创建了一个span,就必须在将来的某个时刻停止它,形成一个闭环; - Trace:树形结构的一组span。
- Annotation/Event:用于记录每个时间点的事件
cs:client sent。客户端发起一个请求,标记一个span的开始;sr:server received。服务端接收到一个请求并开始处理这个请求,用sr的timestamp减去cs的timestamp,表示网络延迟的时间;ss:server sent。服务端完成处理请求,把处理结果返回给客户端时,ss的timestamp减去sr的timestamp,表示服务端处理的时间;cr:client received。客户端接收到服务端响应后记录的时间,标记span的结束。cr的timestamp减去cs的timestamp,表示整个请求话费的时间。
使用
Span 生命周期
最常用的接口:
org.springframework.cloud.sleuth.Tracer:使用 Tracer,您可以创建捕获请求关键路径的root spanorg.springframework.cloud.sleuth.Span:span是需要启动和停止的单个工作单元。包含定时信息、事件和标记
Span生命周期的操作:
- start:当您开始一个
span时,将分配它的名称并记录开始时间戳 - end:span结束
- continue:span被继续,比如在另一个线程中继续
- create with explicit parent:创建一个新的span,并设置它的父节点
创建和结束 spans
可以使用 Tracer 手动创建 spans:
// 创建一个新的span,如果这个线程中已经有一个span,它将成为新span的父线程
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的名字要明确,但不要超过50个字符,要简明最好
继续使用 spans
有时不用创建新的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();
}
使用明确的父span,创建一个span
当一个父span A运行在一个线程中,而你想在一个新的线程中创建另一个span B时,可以调用 Tracer.nextSpan(),使得A成为B的父节点。
// 在线程Y中接收到线程X中新建的"initialSpan”,“initialSpan”将成为线程Y中新建span的父span
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();
}
}
你也可以使用
Tracer.nextSpan(Span parentSpan)版本显式提供父跨度
命名spans
span的默认命名规则:
- controller-method-name:接收到来自controllerMethodName时使用的命名格式
- asyc:当Callable和Runnable完成的操作使用,幸运的是,可以显示的命名
- @Scheduled:方法被该注解注释时使用的名字
@SpanName注解
可以使用@SpanName注释显式地命名span:
@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();
toString() 方法
有时使用Runnable和Callable执行异步方法是,都是匿名的,没办法使用 @SpanName注解匿名方法,这时候可以使用 toString()方法:
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();
使用注解管理spans
创建新的span
除了使用前文的方式创建新的span,还可以使用注解@NewSpan注解,同时,可以使用@SpanTag注解添加标记。
// 命名默认和方法名相同,即 testMethod
@NewSpan
void testMethod();
// 也可以指定名字
@NewSpan("customNameOnTestMethod4")
void testMethod4();
可以组合指定 tag和span标记:
// 定义span,名字是 customNameOnTestMethod5
// 定义tag,key是 testTag
@NewSpan(name = "customNameOnTestMethod5")
void testMethod5(@SpanTag("testTag") String param);
// 执行方法时,参数test会成为tag的value
this.testBean.testMethod5("test");
可以在接口和实现类的方法上都使用span,如果同时存在,实现类上的生效。
@NewSpan(name = "customNameOnTestMethod3")
@Override
public void testMethod3() {
}
继续传递span
如果想在已经存在的span上添加tags或者注解,可以使用 @ContinueSpan,例如:
// 给span添加log日志testMethod11.before和testMethod11.after,如果返回异常,会创建日志 testMethod11.afterFailure
// 添加tag,key是testTag11
@ContinueSpan(log = "testMethod11")
void testMethod11(@SpanTag("testTag11") String param);
// tag的value成为test
this.testBean.testMethod11("test");
this.testBean.testMethod13();
高级span设置
通过@SpanTag提供的三种创建的标签方式,优先级:
- 尝试使用TagValueResolver类型的bean和提供的名称
- 如果未指定名称,则通过SPEL表达式分析
- 如果表达式不满足要求,则使用toString()返回的值