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 {@Overridepublic 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 requestTraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());if (context != null) {// A forwarded request might end up on another thread, so make sure it is scopedScope 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 contextrequest.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 {@Overridepublic 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 {@Overridepublic 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 相关代码
@Aspectpublic 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 {@Overridepublic 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);}}}

