Spring Cloud Sleuth 通过埋点的方式实现分布式链路追踪。
一、Sleuth 支持追踪的组件
通过上面的配置类能够找到 Spring Cloud Sleuth 支持追踪的组件
1.1、Schedule Trace 测试
zuul-client
定时请求 zuul-server
服务。
1.2、RestTemplate 调用 Trace 测试
zuul-client
通过 RestTemplate 发送 HTTP 请求 zuul-server
接口。
1.3、Feign 调用 Trace 测试
zuul-client
通过 feign 发起请求调用 zuul-server
服务。
1.4、Zuul Trace 测试
通过路由调用上面 Feign 测试中的接口,访问路径为: http://localhost:28080/zuul-client/trace-test/feign
。
通过 zuul
路由到 zuul-client
然后在调用 zuul-server
。
1.5、更多自行测试
二、针对个别组件 Sleuth 实现进行剖析
虽然 sleuth 实现的组件很多,但是实现的方式基本都是一致的,通过注入提供有 trace 功能的类实现埋点。
2.1、Zuul + Feign trace 原理
通过一个 zuul -> client -> server 的调用案例,分析 zuul 和 Fiegn Trace 的实现原理
代码调用如下
生成的 trace 链路如下
2.1.1、TracingFilter
创建 span
Spring Boot 服务本身就是一个 Spring MVC Web 应用,所有处理请求由 DispatcherServlet 来完成。对此,Spring cloud 提供了 TracingFilter
servlet filter 用来创建 span
,
TracingFilter
相关代码如下:
public final class TracingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = servlet.httpResponse(response);
// Prevent duplicate spans for the same request
TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());
if (context != null) {
// A forwarded request might end up on another thread, so make sure it is scoped
Scope scope = currentTraceContext.maybeScope(context);
try {
chain.doFilter(request, response);
} finally {
scope.close();
}
return;
}
Span span = handler.handleReceive(extractor, httpRequest);
// Add attributes for explicit access to customization or span context
request.setAttribute(SpanCustomizer.class.getName(), span.customizer());
request.setAttribute(TraceContext.class.getName(), span.context());
......
}
}
2.1.2、zuul 对 span 的标记(annotation)变更y已经处理:相关类 TracePostZuulFilter
TracePostZuulFilter
为 post 类型, order = 0 ,在 zuul 处理完请求之后,对span
进行最后的标记(annotation)
部分代码如下:
class TracePostZuulFilter extends ZuulFilter {
@Override
public Object run() {
if (log.isDebugEnabled()) {
log.debug("Marking current span as handled");
}
HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
Throwable exception = RequestContext.getCurrentContext().getThrowable();
Span currentSpan = this.tracer.currentSpan();
this.handler.handleSend(response, exception, currentSpan);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + currentSpan);
}
return null;
}
}
2.1.3、Feign 对 span 的标记(annotation)变更处理 TracingFeignClient
部分代码如下:
final class TracingFeignClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Map<String, Collection<String>> headers = new HashMap<>(request.headers());
Span span = handleSend(headers, request, null);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + span);
}
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
response = this.delegate.execute(modifiedRequest(request, headers), options);
return response;
}
catch (IOException | RuntimeException | Error e) { ...... }
finally {
handleReceive(span, response, error);
if (log.isDebugEnabled()) {
log.debug("Handled receive of " + span);
}
}
}
}
2.2、Schedule trace 实现原理
相关实现在 sleuth 项目中的位置
通过上图能够知道相关类 TraceSchedulingAspect
,看到该类也能够猜到通过 AOP 的方式实现的 Trace
来看看 TraceSchedulingAspect
相关代码
@Aspect
public class TraceSchedulingAspect {
@Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))")
public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
if (this.skipPattern.matcher(pjp.getTarget().getClass().getName()).matches()) {
return pjp.proceed();
}
String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName());
Span span = startOrContinueRenamedSpan(spanName);
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) {
span.tag(CLASS_KEY, pjp.getTarget().getClass().getSimpleName());
span.tag(METHOD_KEY, pjp.getSignature().getName());
return pjp.proceed();
}
catch (Throwable ex) {
String message = ex.getMessage() == null ? ex.getClass().getSimpleName()
: ex.getMessage();
span.tag("error", message);
throw ex;
}
finally {
span.finish();
}
}
private Span startOrContinueRenamedSpan(String spanName) {
Span currentSpan = this.tracer.currentSpan();
if (currentSpan != null) {
return currentSpan.name(spanName);
}
return this.tracer.nextSpan().name(spanName);
}
}
直接通过 AOP 切注解 @Scheduled
2.3、RestTemplate trace 实现原理
Spring Cloud Sleuth 提供了 TracingClientHttpRequestInterceptor
,该类实现接口 ClientHttpRequestInterceptor
是 RestTemplate
请求拦截器。
TracingClientHttpRequestInterceptor
相关代码如下:
public final class TracingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
Span span = handler.handleSend(injector, request.getHeaders(), request);
ClientHttpResponse response = null;
Throwable error = null;
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
return response = execution.execute(request, body);
} catch (IOException | RuntimeException | Error e) {
error = e;
throw e;
} finally {
handler.handleReceive(response, error, span);
}
}
}