sleuth+zipkin系列之禁用URI调用链(八)

问题背景

在实际生产中,我们有可能会碰到有些接口,有些URI我们不想追踪,不想产生追踪数据,这个时候可以使用sleuth的追踪跳过的机制,默认sleuth已经禁用了如下URI,下面的URI访问系统的时候是不会被追踪的。

spring:
  sleuth:
    web:
      skip-pattern: "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream"
 

每个URI通过| 隔开,由于sleuth生成span的地方实在traceFilter中生成的,traceFilter默认是所有路径都会拦截的,只要拦截到了就会生成span,只不过这个span会不会上报,这就要看两个方面:

1.yaml中skip-pattern的配置,如果uri在这个里面配置了,那么就不会上报。

2.Sampler defaultTraceSampler()的配置,这个类里面有个isSample方法,如果返回false,那么就不会上报,后面会单独讲这一块。

效果演示

情景1

在这里插入图片描述

A 调用 B,再调用C

在系统A里面添加skip-pattern配置

spring:
  sleuth:
    web:
      skip-pattern: "/api-docs.*|/autoconfig|/configprops|/dump|/health|/info|/metrics.*|/mappings|/trace|/swagger.*|.*\\.png|.*\\.css|.*\\.js|.*\\.html|/favicon.ico|/hystrix.stream|/test"

skip-pattern的最后面添加了test, 也就是说uri为test的接口调用不追踪。 通过测试发现,A>B>C这一次调用链都没有被收集到。

情景2

在这里插入图片描述

A 调用 B,再调用C

在系统B里面添加skip-pattern配置,配置我就不贴上了。

测试结果:
调用链仅收集到A>B , 因为在B系统里面,请求B的URI过滤掉了,因此整个追踪链路在B系统就断掉了,C也不会上传他的span了。

###原理解析

代码入口:org.springframework.cloud.sleuth.instrument.web.TraceFilter

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
			FilterChain filterChain) throws IOException, ServletException {
		if (!(servletRequest instanceof HttpServletRequest) || !(servletResponse instanceof HttpServletResponse)) {
			throw new ServletException("Filter just supports HTTP requests");
		}
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
  		// 获取请求的URL
		String uri = this.urlPathHelper.getPathWithinApplication(request);
  		// 判断是否需要跳过,跳过的意思就是创建的span不上报
		boolean skip = this.skipPattern.matcher(uri).matches()
				|| Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME));
		// 省略代码。。。。。
		
		try {
          	// 创建span
			spanFromRequest = createSpan(request, skip, spanFromRequest, name);
			filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest));
		} catch (Throwable e) {
			// 省略代码。。
		} finally {
			// 省略代码。。。。
	}

当一个请求进入系统的时候,首先会经过TraceFilter , 进行span的创建和上报

步骤说明:

1.解析当前请求的URI

2.this.skipPattern.matcher(uri).matches(),这行代码的意思就是判断当前请求的URI是否在skip-pattern里面,匹配到了就返回true。

3.获取请求头里面X-B3-Sampled的值,当X-B3-Sampled==0的时候表示不上报,比如我们上面举的例子,系统A调用系统B的时候,系统A本身就已经

被限制了,说是不上报span, 那么他在请求系统B的时候,就会往请求头里面塞个值,X-B3-Sampled=0 , 这样系统A往下调用的那些系统都不会进行上报span了

4.上面三步,主要是确定可skip的值,true还是false,具体应用的时候还是在createSpan的时候会使用

createSpan

private Span createSpan(HttpServletRequest request,
			boolean skip, Span spanFromRequest, String name) {
		if (spanFromRequest != null) {
			if (log.isDebugEnabled()) {
				log.debug("Span has already been created - continuing with the previous one");
			}
			return spanFromRequest;
		}
  		// 解析出请求里面的traceID,spanId ,parentId
		Span parent = spanExtractor().joinTrace(new HttpServletRequestTextMap(request));
  		// parent不为空,表示该请求是从上游系统调用过来的。
		if (parent != null) {
			if (log.isDebugEnabled()) {
				log.debug("Found a parent span " + parent + " in the request");
			}
          	// 添加请求tag进入span
			addRequestTagsForParentSpan(request, parent);
			spanFromRequest = parent;
          	// 将spanFromRequest放入ThreadLocal中,并且记录日志
			tracer().continueSpan(spanFromRequest);
			if (parent.isRemote()) { // 是否是远程调用过来的。只要parent不为空,这个基本上就是true了
              	// 设置事件,server receive ,表示服务收到了,记录服务收到请求的时间
				parent.logEvent(Span.SERVER_RECV);
			}
			request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
			if (log.isDebugEnabled()) {
				log.debug("Parent span is " + parent + "");
			}
		} else {
          	// 如果skip==true, 那么设置当前span的上报器为NeverSampler.INSTANCE , 表示不上报span
			if (skip) {
				spanFromRequest = tracer().createSpan(name, NeverSampler.INSTANCE);
			}
			else {
              	// skip==false , 设置他的上报器为new AlwaysSampler() , 表示本次的span是会上报的
				String header = request.getHeader(Span.SPAN_FLAGS);
				if (Span.SPAN_SAMPLED.equals(header)) {
					spanFromRequest = tracer().createSpan(name, new AlwaysSampler());
				} else {
					spanFromRequest = tracer().createSpan(name);
				}
			}
          	// server receive时间,记录服务收到请求的时间
			spanFromRequest.logEvent(Span.SERVER_RECV);
			request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
			if (log.isDebugEnabled()) {
				log.debug("No parent span present - creating a new span");
			}
		}
		return spanFromRequest;
	}

上面的那一大串代码,跟本次分析的相关的,就是当parent等于null的那个else里面的代码, parent不等于空的时候,是否上报请求,跟parent一样,parent上报,那就上报。

当skip==true的时候,会将span的上报器设置为naverSampler,也就是从不上报span。

设置好Sampler之后,程序在哪里使用呢? 在createSpan里面会调用下面这个方法

private Span sampledSpan(Span span, Sampler sampler) {
		if (!sampler.isSampled(span)) {
			// Copy everything, except set exportable to false
			return Span.builder()
					.begin(span.getBegin())
					.traceIdHigh(span.getTraceIdHigh())
					.traceId(span.getTraceId())
					.spanId(span.getSpanId())
					.name(span.getName())
					.exportable(false).build();
		}
		return span;
	}

调用sampler的isSampled方法,如果返回false,那么这是exportable = false

NeverSampler的isSampled至始至终都是返回false

public class NeverSampler implements Sampler {

	public static final NeverSampler INSTANCE = new NeverSampler();

	@Override
	public boolean isSampled(Span span) {
		return false;
	}
}

设置好span的属性exportable 等于false之后,在最后closeSpan时,准备将其放入队列里面供异步线程(从队列里面取数据,发送给kafka)消费时。会对span的exportable属性做判断。

代码入口:org.springframework.cloud.sleuth.stream.StreamSpanReporter

@Override
	public void report(Span span) {
		Span spanToReport = span;
      	// 判断exportable  的属性值,只有等于true的时候才会进入if结构
		if (spanToReport.isExportable()) {
			try {
				if (this.environment != null) {
					processLogs(spanToReport);
				}
              	// 调用span的调整器,对span做最后的调整
				for (SpanAdjuster adjuster : this.spanAdjusters) {
					spanToReport = adjuster.adjust(spanToReport);
				}
              	// 加入队列供异步线程消费
				this.queue.add(spanToReport);
			} catch (Exception e) {
				this.spanMetricReporter.incrementDroppedSpans(1);
				if (log.isDebugEnabled()) {
					log.debug("The span " + spanToReport + " will not be sent to Zipkin due to [" + e + "]");
				}
			}
		} else {	
          	// 等于false,不做任何处理
			if (log.isDebugEnabled()) {
				log.debug("The span " + spanToReport + " will not be sent to Zipkin due to sampling");
			}
		}
	}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u012394095/article/details/82753045