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

# Sleuth 支持追踪的组件

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

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

# Schedule Trace 测试

zuul-client 定时请求 zuul-server 服务。

相关 trace 采集信息如下:

# RestTemplate 调用 Trace 测试

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

相关 trace 采集信息如下:

# Feign 调用 Trace 测试

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

相关 trace 采集信息如下:

# Zuul Trace 测试

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

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

相关 trace 采集信息如下

# 更多自行测试

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

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

# Zuul + Feign trace 原理

brave 相关 Github 代码

zuul-工作流程

Feign Client 请求调用

通过一个 zuul -> client -> server 的调用案例,分析 zuul 和 Fiegn Trace 的实现原理

代码调用如下

生成的 trace 链路如下

链路对应到数据库 zipkin_spans 中3条记录

# 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());
    ......
  }
}

# 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;
	}
}

# 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);
			}
		}
	}
}

# 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

# RestTemplate trace 实现原理

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

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);
    }
  }
}
精彩内容推送,请关注公众号!
最近更新时间: 4/27/2020, 9:11:28 PM