code.7z

Spring Cloud Sleuth 通过埋点的方式实现分布式链路追踪。

一、Sleuth 支持追踪的组件

01.png
通过上面的配置类能够找到 Spring Cloud Sleuth 支持追踪的组件

针对个别组件进行简单测试

1.1、Schedule Trace 测试

02.png
zuul-client 定时请求 zuul-server 服务。

相关 trace 采集信息如下:
03.png

1.2、RestTemplate 调用 Trace 测试

04.png
zuul-client 通过 RestTemplate 发送 HTTP 请求 zuul-server 接口。

相关 trace 采集信息如下:
05.png

1.3、Feign 调用 Trace 测试

06.png
zuul-client 通过 feign 发起请求调用 zuul-server 服务。

相关 trace 采集信息如下:
07.png

1.4、Zuul Trace 测试

08.png
通过路由调用上面 Feign 测试中的接口,访问路径为: http://localhost:28080/zuul-client/trace-test/feign

通过 zuul 路由到 zuul-client 然后在调用 zuul-server

相关 trace 采集信息如下
09.png

1.5、更多自行测试

二、针对个别组件 Sleuth 实现进行剖析

虽然 sleuth 实现的组件很多,但是实现的方式基本都是一致的,通过注入提供有 trace 功能的类实现埋点。

2.1、Zuul + Feign trace 原理

brave 相关 Github 代码 《zuul-工作流程》 《Feign Client 请求调用》

通过一个 zuul -> client -> server 的调用案例,分析 zuul 和 Fiegn Trace 的实现原理
10.png
代码调用如下
11.png
生成的 trace 链路如下

12.png
链路对应到数据库 zipkin_spans 中3条记录
13.png

2.1.1、TracingFilter 创建 span

Spring Boot 服务本身就是一个 Spring MVC Web 应用,所有处理请求由 DispatcherServlet 来完成。对此,Spring cloud 提供了 TracingFilter servlet filter 用来创建 span

TracingFilter 相关代码如下:

  1. public final class TracingFilter implements Filter {
  2. @Override
  3. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  4. throws IOException, ServletException {
  5. HttpServletRequest httpRequest = (HttpServletRequest) request;
  6. HttpServletResponse httpResponse = servlet.httpResponse(response);
  7. // Prevent duplicate spans for the same request
  8. TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());
  9. if (context != null) {
  10. // A forwarded request might end up on another thread, so make sure it is scoped
  11. Scope scope = currentTraceContext.maybeScope(context);
  12. try {
  13. chain.doFilter(request, response);
  14. } finally {
  15. scope.close();
  16. }
  17. return;
  18. }
  19. Span span = handler.handleReceive(extractor, httpRequest);
  20. // Add attributes for explicit access to customization or span context
  21. request.setAttribute(SpanCustomizer.class.getName(), span.customizer());
  22. request.setAttribute(TraceContext.class.getName(), span.context());
  23. ......
  24. }
  25. }

2.1.2、zuul 对 span 的标记(annotation)变更y已经处理:相关类 TracePostZuulFilter

TracePostZuulFilter 为 post 类型, order = 0 ,在 zuul 处理完请求之后,对 span 进行最后的标记(annotation)

部分代码如下:

  1. class TracePostZuulFilter extends ZuulFilter {
  2. @Override
  3. public Object run() {
  4. if (log.isDebugEnabled()) {
  5. log.debug("Marking current span as handled");
  6. }
  7. HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
  8. Throwable exception = RequestContext.getCurrentContext().getThrowable();
  9. Span currentSpan = this.tracer.currentSpan();
  10. this.handler.handleSend(response, exception, currentSpan);
  11. if (log.isDebugEnabled()) {
  12. log.debug("Handled send of " + currentSpan);
  13. }
  14. return null;
  15. }
  16. }

2.1.3、Feign 对 span 的标记(annotation)变更处理 TracingFeignClient

部分代码如下:

  1. final class TracingFeignClient implements Client {
  2. @Override
  3. public Response execute(Request request, Request.Options options) throws IOException {
  4. Map<String, Collection<String>> headers = new HashMap<>(request.headers());
  5. Span span = handleSend(headers, request, null);
  6. if (log.isDebugEnabled()) {
  7. log.debug("Handled send of " + span);
  8. }
  9. try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
  10. response = this.delegate.execute(modifiedRequest(request, headers), options);
  11. return response;
  12. }
  13. catch (IOException | RuntimeException | Error e) { ...... }
  14. finally {
  15. handleReceive(span, response, error);
  16. if (log.isDebugEnabled()) {
  17. log.debug("Handled receive of " + span);
  18. }
  19. }
  20. }
  21. }

2.2、Schedule trace 实现原理

相关实现在 sleuth 项目中的位置
14.png
通过上图能够知道相关类 TraceSchedulingAspect ,看到该类也能够猜到通过 AOP 的方式实现的 Trace

来看看 TraceSchedulingAspect 相关代码

  1. @Aspect
  2. public class TraceSchedulingAspect {
  3. @Around("execution (@org.springframework.scheduling.annotation.Scheduled * *.*(..))")
  4. public Object traceBackgroundThread(final ProceedingJoinPoint pjp) throws Throwable {
  5. if (this.skipPattern.matcher(pjp.getTarget().getClass().getName()).matches()) {
  6. return pjp.proceed();
  7. }
  8. String spanName = SpanNameUtil.toLowerHyphen(pjp.getSignature().getName());
  9. Span span = startOrContinueRenamedSpan(spanName);
  10. try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span.start())) {
  11. span.tag(CLASS_KEY, pjp.getTarget().getClass().getSimpleName());
  12. span.tag(METHOD_KEY, pjp.getSignature().getName());
  13. return pjp.proceed();
  14. }
  15. catch (Throwable ex) {
  16. String message = ex.getMessage() == null ? ex.getClass().getSimpleName()
  17. : ex.getMessage();
  18. span.tag("error", message);
  19. throw ex;
  20. }
  21. finally {
  22. span.finish();
  23. }
  24. }
  25. private Span startOrContinueRenamedSpan(String spanName) {
  26. Span currentSpan = this.tracer.currentSpan();
  27. if (currentSpan != null) {
  28. return currentSpan.name(spanName);
  29. }
  30. return this.tracer.nextSpan().name(spanName);
  31. }
  32. }

直接通过 AOP 切注解 @Scheduled

2.3、RestTemplate trace 实现原理

Spring Cloud Sleuth 提供了 TracingClientHttpRequestInterceptor ,该类实现接口 ClientHttpRequestInterceptorRestTemplate 请求拦截器。

TracingClientHttpRequestInterceptor 相关代码如下:

  1. public final class TracingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
  2. @Override
  3. public ClientHttpResponse intercept(HttpRequest request, byte[] body,
  4. ClientHttpRequestExecution execution) throws IOException {
  5. Span span = handler.handleSend(injector, request.getHeaders(), request);
  6. ClientHttpResponse response = null;
  7. Throwable error = null;
  8. try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
  9. return response = execution.execute(request, body);
  10. } catch (IOException | RuntimeException | Error e) {
  11. error = e;
  12. throw e;
  13. } finally {
  14. handler.handleReceive(response, error, span);
  15. }
  16. }
  17. }