原理介绍
Sleuth通过Trace定义一次业务调用链,根据它的信息,我们就知道多少个系统参与了该业务处理。
Span用来记录系统间的调用顺序和时间戳信息。
Trace和Span的信息经过整合,就能知道该业务的完整调用链。
Brave和Zipkin
Brave是一个用于捕捉分布式系统之间调用信息的工具库,然后将这些信息以Span的形式发送给Zipkin。
参考:https://github.com/openzipkin/brave
从2.0.0版本开始,Sleuth不再自己存储上下文信息,而是使用Brave作为调用链工具库,并且遵循Brave的命名和标记惯例。
如果你想延用老版本的使用方式,可以配置spring.sleuth.http.legacy.enabled=true
。
Zipkin是一个基于Google Dapper论文设计的分布式跟踪系统,它收集系统的延时数据
并提供展示界面,以便用户排查问题。
参考:https://github.com/openzipkin/zipkin
Zipkin启动方式
- java -jar zipkin.jar
- 还支持源码部署和Docker镜像启动。
- Zipkin支持各种持久化方式,默认存储在内存中
- 部署后,默认的启动端口位9411,http://localhost:9411/
使用
引入spring-cloud-starter-sleuth
依赖之后,我们的日志组件可以自动打印Span信息。可以随着feign、restTemplate往服务端传递,也可以在父子线程间传递。
依赖
<!--sleuth-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Sleuth对feign的支持
- Feign提供了feign.Client接口以便开发者自定义远程调用功能。
- Sleuth使用了TracingFeignClient实现Feign接口,在Http调用前,在Header中添加Span信息。
public Response execute(Request request, Options options) throws IOException {
Map<String, Collection<String>> headers = new HashMap(request.headers());
Span span = this.handleSend(headers, request, (Span)null);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + span);
}
Response response = null;
IOException error = null;
Object var9;
try {
SpanInScope ws = this.tracer.withSpanInScope(span);
Throwable var8 = null;
try {
var9 = response = this.delegate.execute(this.modifiedRequest(request, headers), options);
} catch (Throwable var27) {
var9 = var27;
var8 = var27;
throw var27;
} finally {
if (ws != null) {
if (var8 != null) {
try {
ws.close();
} catch (Throwable var26) {
var8.addSuppressed(var26);
}
} else {
ws.close();
}
}
}
} catch (RuntimeException | Error | IOException var29) {
error = var29;
throw var29;
} finally {
this.handleReceive(span, response, error);
if (log.isDebugEnabled()) {
log.debug("Handled receive of " + span);
}
}
return (Response)var9;
}
Sleuth对RestTemplate的支持
对RestTemplate的支持并不是临时初始化的,需要将RestTemplate注册成一个Bean。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package brave.spring.web;
import brave.Span;
import brave.Tracer;
import brave.Tracing;
import brave.Tracer.SpanInScope;
import brave.http.HttpClientAdapter;
import brave.http.HttpClientHandler;
import brave.http.HttpTracing;
import brave.propagation.Propagation.Setter;
import brave.propagation.TraceContext.Injector;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public final class TracingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
static final Setter<HttpHeaders, String> SETTER = new Setter<HttpHeaders, String>() {
public void put(HttpHeaders carrier, String key, String value) {
carrier.set(key, value);
}
public String toString() {
return "HttpHeaders::set";
}
};
final Tracer tracer;
final HttpClientHandler<HttpRequest, ClientHttpResponse> handler;
final Injector<HttpHeaders> injector;
public static ClientHttpRequestInterceptor create(Tracing tracing) {
return create(HttpTracing.create(tracing));
}
public static ClientHttpRequestInterceptor create(HttpTracing httpTracing) {
return new TracingClientHttpRequestInterceptor(httpTracing);
}
@Autowired
TracingClientHttpRequestInterceptor(HttpTracing httpTracing) {
this.tracer = httpTracing.tracing().tracer();
this.handler = HttpClientHandler.create(httpTracing, new TracingClientHttpRequestInterceptor.HttpAdapter());
this.injector = httpTracing.tracing().propagation().injector(SETTER);
}
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
Span span = this.handler.handleSend(this.injector, request.getHeaders(), request);
ClientHttpResponse response = null;
IOException error = null;
Object var9;
try {
SpanInScope ws = this.tracer.withSpanInScope(span);
Throwable var8 = null;
try {
var9 = response = execution.execute(request, body);
} catch (Throwable var27) {
var9 = var27;
var8 = var27;
throw var27;
} finally {
if (ws != null) {
if (var8 != null) {
try {
ws.close();
} catch (Throwable var26) {
}
} else {
ws.close();
}
}
}
} catch (RuntimeException | Error | IOException var29) {
error = var29;
throw var29;
} finally {
this.handler.handleReceive(response, error, span);
}
return (ClientHttpResponse)var9;
}
static final class HttpAdapter extends HttpClientAdapter<HttpRequest, ClientHttpResponse> {
HttpAdapter() {
}
public String method(HttpRequest request) {
return request.getMethod().name();
}
public String url(HttpRequest request) {
return request.getURI().toString();
}
public String requestHeader(HttpRequest request, String name) {
Object result = request.getHeaders().getFirst(name);
return result != null ? result.toString() : "";
}
public Integer statusCode(ClientHttpResponse response) {
try {
return response.getRawStatusCode();
} catch (IOException var3) {
return null;
}
}
}
}
Sleuth对多线程的支持
Sleuth提供了三种多线程实现
- LazyTraceExecutor
- TraceableExecutorService
- TraceableScheduleExecutorService
它们可以在创建新的任务时新建一个Span。
必须使用这三种多线程实现才有效。