溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

springcloud-sleuth源碼怎么解析2-TraceFilter

發(fā)布時間:2021-10-20 10:44:28 來源:億速云 閱讀:245 作者:柒染 欄目:大數(shù)據(jù)

本篇文章給大家分享的是有關(guān)springcloud-sleuth源碼怎么解析2-TraceFilter,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

基于spring cloud 1.2.1版本

將分析server接收一個請求,trace究竟是怎么處理的。

span的生命周期

首先介紹下一個span的生命周期:

  • start
    創(chuàng)建一個span,這時候會記錄創(chuàng)建時間以及設(shè)置span name。如果當(dāng)前線程已經(jīng)存在一個span,則創(chuàng)建的新的span是childSpan。

// Start a span. If there was a span present in this thread it will become
// the `newSpan`'s parent.
Span newSpan = this.tracer.createSpan("calculateTax");
try {
	// ...
	// You can tag a span
	this.tracer.addTag("taxValue", taxValue);
	// ...
	// You can log an event on a span
	newSpan.logEvent("taxCalculated");
} finally {
	// Once done remember to close the span. This will allow collecting
	// the span to send it to Zipkin
	this.tracer.close(newSpan);
}
  • close
    如果一個span已經(jīng)準(zhǔn)備好將自身發(fā)送到zipkin server或者其他collect的時候,便會調(diào)用close方法,記錄endTime,上報span,然后從當(dāng)前線程中移除span。

  • continue
    將一個新的span設(shè)置到當(dāng)前線程中,成為continueSpan。該方法作用是傳遞不同線程之間的span。對于一些異步處理代碼,就需要 將span設(shè)置到異步處理線程中了。

Span continuedSpan = this.tracer.continueSpan(spanToContinue);
  • detach
    從當(dāng)前線程中刪除span,剝離出去,但不會stop或者close span。一般跟continue方法結(jié)合使用。

// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X
Span continuedSpan = this.tracer.continueSpan(initialSpan);
try {
	// ...
	// You can tag a span
	this.tracer.addTag("taxValue", taxValue);
	// ...
	// You can log an event on a span
	continuedSpan.logEvent("taxCalculated");
} finally {
	// Once done remember to detach the span. That way you'll
	// safely remove it from the current thread without closing it
	this.tracer.detach(continuedSpan);
}
  • create with explicit parent
    創(chuàng)建一個span并且指定它的parent span。該方法的使用場景是在當(dāng)前線程中想創(chuàng)建一個span,但parent span存在另一個線程當(dāng)中,這樣你 就可以獲取到parent span,明確指定該span為要創(chuàng)建的span的parent。

// let's assume that we're in a thread Y and we've received
// the `initialSpan` from thread X. `initialSpan` will be the parent
// of the `newSpan`
Span newSpan = this.tracer.createSpan("calculateCommission", initialSpan);
try {
	// ...
	// You can tag a span
	this.tracer.addTag("commissionValue", commissionValue);
	// ...
	// You can log an event on a span
	newSpan.logEvent("commissionCalculated");
} finally {
	// Once done remember to close the span. This will allow collecting
	// the span to send it to Zipkin. The tags and events set on the
	// newSpan will not be present on the parent
	this.tracer.close(newSpan);
}

下面介紹TraceFilter過濾器,它攔截所有請求,我們直接看它的doFilter方法。

TraceFilter.doFilter:

@Override
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;
	//首先從request獲取到uri
	String uri = this.urlPathHelper.getPathWithinApplication(request);
	//判斷是否忽略本次trace。根據(jù)兩個條件判斷是否忽略本次trace:
	//A、根據(jù)skipPattern判斷此uri是否是skip uri,如果是返回true
	//B、從request、response的head中獲取X-B3-Sampled屬性,如果值為0則返回true,即不進(jìn)行采樣
	boolean skip = this.skipPattern.matcher(uri).matches()
			|| Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME));
	//從request中g(shù)etAttribute span。表示在一個request中,如果發(fā)生了轉(zhuǎn)發(fā)那直接可以在request中獲取span,不需要重新生成。		
	Span spanFromRequest = getSpanFromAttribute(request);
	if (spanFromRequest != null) {
	    //不為空的話則continueSpan,下面看看continueSpan方法。
		continueSpan(request, spanFromRequest);
	}
	if (log.isDebugEnabled()) {
		log.debug("Received a request to uri [" + uri + "] that should not be sampled [" + skip + "]");
	}
	// in case of a response with exception status a exception controller will close the span
	//正如上面的注釋所說,這是請求出現(xiàn)異常時,跳轉(zhuǎn)到異常controller時處理邏輯,然后關(guān)閉span立即結(jié)束filter。
	if (!httpStatusSuccessful(response) && isSpanContinued(request)) {
		Span parentSpan = parentSpan(spanFromRequest);
		processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest);
		return;
	}
	//設(shè)置span name
	String name = HTTP_COMPONENT + ":" + uri;
	Throwable exception = null;
	try {
	    //根據(jù)request創(chuàng)建span,下面分析createSpan代碼。
		spanFromRequest = createSpan(request, skip, spanFromRequest, name);
		//這里會觸發(fā)springmvc的trace攔截器TraceHandlerInterceptor的一些方法,下一章會分析。
		filterChain.doFilter(request, new TraceHttpServletResponse(response, spanFromRequest));
	} catch (Throwable e) {
		exception = e;
		this.tracer.addTag(Span.SPAN_ERROR_TAG_NAME, ExceptionUtils.getExceptionMessage(e));
		throw e;
	} finally {
	    //對于異步request則不進(jìn)行處理
		if (isAsyncStarted(request) || request.isAsyncStarted()) {
			if (log.isDebugEnabled()) {
				log.debug("The span " + spanFromRequest + " will get detached by a HandleInterceptor");
			}
			// TODO: how to deal with response annotations and async?
			return;
		}
		//如果該請求沒有被spring mvc的trace攔截器攔截到,則會人工的生成一個lc類型span,作為spanFromRequest的child span,
		//彌補(bǔ)springmvc的trace攔截器缺失的部分,這樣能保證對于zipkin來說是一個合理的調(diào)用鏈路。
		spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip);
		//分離獲取關(guān)閉span,最后來分析下該方法
		detachOrCloseSpans(request, response, spanFromRequest, exception);
	}
}

TraceFilter.continueSpan:

private void continueSpan(HttpServletRequest request, Span spanFromRequest) {
        //tracer的continueSpan方法的作用是將新的span設(shè)置到當(dāng)前線程中。
        //比如span a 在線程X中,span b在線程Y中,現(xiàn)在上下文處于線程b中,然后操作continueSpan(a),
        //即將線程Y中的span b替換成span a,然后span a中的saved span屬性設(shè)置成span b,即設(shè)置當(dāng)前線程span之前的current span。
        //下面分析下continueSpan方法。
		this.tracer.continueSpan(spanFromRequest);
		request.setAttribute(TraceRequestAttributes.SPAN_CONTINUED_REQUEST_ATTR, "true");
		if (log.isDebugEnabled()) {
			log.debug("There has already been a span in the request " + spanFromRequest);
		}
	}

DefaultTracer.continueSpan:

public Span continueSpan(Span span) {
		if (span != null) {
		    //日志組件,主要用于MDC輸出的
			this.spanLogger.logContinuedSpan(span);
		} else {
			return null;
		}
		//createContinuedSpan方法第一個參數(shù)span是request中保存的span,或者其他上下文傳遞下來的。
		//第二個span,SpanContextHolder.getCurrentSpan()是從ThreadLocal獲取當(dāng)前線程中的span。
		//下面看下createContinuedSpan方法
		Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan());
		//將新的span保存到當(dāng)前線程中
		SpanContextHolder.setCurrentSpan(newSpan);
		return newSpan;
	}

//如果當(dāng)前線程span為空且被傳遞過來的span的saved span屬性不為空,則設(shè)置新的saved span為被傳遞過來的span的saved span,
//否則saved span使用當(dāng)前線程中的span。
private Span createContinuedSpan(Span span, Span saved) {
		if (saved == null && span.getSavedSpan() != null) {
			saved = span.getSavedSpan();
		}
		return new Span(span, saved);
	}

對于new Span(span, saved)這種構(gòu)造span的形式我們來分析下saved span有何作用:
saved的span是在創(chuàng)建該新的span之前就已經(jīng)存在當(dāng)前線程中的span。有兩種情況會調(diào)用該api:

  1. 正如之前所說的,span從線程X復(fù)制到線程Y中

  2. 當(dāng)前線程中已有span,然后創(chuàng)建child span,child span的saved span就是parent span

TraceFilter.createSpan

/**
 * Creates a span and appends it as the current request's attribute
 */
private Span createSpan(HttpServletRequest request,
		boolean skip, Span spanFromRequest, String name) {
	//如果spanFromRequest不為空,即發(fā)生了轉(zhuǎn)發(fā)等情況,那直接返回,不需要創(chuàng)建新的span。
	if (spanFromRequest != null) {
		if (log.isDebugEnabled()) {
			log.debug("Span has already been created - continuing with the previous one");
		}
		return spanFromRequest;
	}
	//抽取request中的header、path等信息創(chuàng)建span,下面將分析joinTrace方法。
	Span parent = this.spanExtractor.joinTrace(new HttpServletRequestTextMap(request));
	//如果成功從request中提取了trace信息,生成了parent
	if (parent != null) {
		if (log.isDebugEnabled()) {
			log.debug("Found a parent span " + parent + " in the request");
		}
	    //正常tags中信息不會在server端添加,而是在client端添加tags。
	    //但是如果request header中不存在span name信息,說明client沒有生成span信息,導(dǎo)致span信息不完整,
	    //那么就需要在server端生成tags。
		addRequestTagsForParentSpan(request, parent);
		spanFromRequest = parent;
		//將生成的span保存到當(dāng)前線程中,詳情看DefaultTracer.continueSpan方法,前面已分析。
		this.tracer.continueSpan(spanFromRequest);
		//判斷該span是否跨進(jìn)程,是的話會加SR標(biāo)識,即span生命周期中server recive階段
		if (parent.isRemote()) {
			parent.logEvent(Span.SERVER_RECV);
		}
		//將span保存到request中。
		request.setAttribute(TRACE_REQUEST_ATTR, spanFromRequest);
		if (log.isDebugEnabled()) {
			log.debug("Parent span is " + parent + "");
		}
	} else {
	    //parent為空需要生成新的span
	    //如果skip為true,則會生成一個不采樣標(biāo)識的span
		if (skip) {
			spanFromRequest = this.tracer.createSpan(name, NeverSampler.INSTANCE);
		}
		else {
		    //根據(jù)request header中的采樣標(biāo)識判斷直接采樣,還是根據(jù)本地設(shè)置的采樣器判斷是否采樣
		    //下面分析下DefaultTracer.createSpan方法
			String header = request.getHeader(Span.SPAN_FLAGS);
			if (Span.SPAN_SAMPLED.equals(header)) {
				spanFromRequest = this.tracer.createSpan(name, new AlwaysSampler());
			} else {
				spanFromRequest = this.tracer.createSpan(name);
			}
		}
		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;
}

ZipkinHttpSpanExtractor.joinTrace

public Span joinTrace(SpanTextMap textMap) {
        //carrier中保存request header、uri信息
		Map<String, String> carrier = TextMapUtil.asMap(textMap);
		//判斷header中是否有Span.SPAN_FLAGS標(biāo)識,且值為1,即需要采樣。
		boolean debug = Span.SPAN_SAMPLED.equals(carrier.get(Span.SPAN_FLAGS));
		if (debug) {
			// we're only generating Trace ID since if there's no Span ID will assume
			// that it's equal to Trace ID
			//header中如果不存在trace id,則生成一個。
			generateIdIfMissing(carrier, Span.TRACE_ID_NAME);
		} else if (carrier.get(Span.TRACE_ID_NAME) == null) {
			// can't build a Span without trace id
			//header中沒有trace id則直接返回null,不能從reqeust中提取信息構(gòu)建span
			return null;
		}
		try {
			String uri = carrier.get(URI_HEADER);
			//根據(jù)uri判斷是夠skip
			boolean skip = this.skipPattern.matcher(uri).matches()
					|| Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME));
			//如果header中span id為空則根據(jù)trace id生成一個,否則直接返回header中的span id。		
			long spanId = spanId(carrier);
			//構(gòu)建span
			return buildParentSpan(carrier, uri, skip, spanId);
		} catch (Exception e) {
			log.error("Exception occurred while trying to extract span from carrier", e);
			return null;
		}
	}

DefaultTracer.createSpan

public Span createSpan(String name, Sampler sampler) {
		String shortenedName = SpanNameUtil.shorten(name);
		Span span;
		//如果本地即當(dāng)前線程已經(jīng)存在span,則創(chuàng)建child span,當(dāng)前線程中的span為parent span
		//如果不存在span,則創(chuàng)建一個完全新的span
		if (isTracing()) {
			span = createChild(getCurrentSpan(), shortenedName);
		}
		else {
			long id = createId();
			span = Span.builder().name(shortenedName)
					.traceIdHigh(this.traceId128 ? createId() : 0L)
					.traceId(id)
					.spanId(id).build();
			if (sampler == null) {
				sampler = this.defaultSampler;
			}
			span = sampledSpan(span, sampler);
			this.spanLogger.logStartedSpan(null, span);
		}
		//將創(chuàng)建的span保存在當(dāng)前線程
		return continueSpan(span);
	}

TraceFilter.detachOrCloseSpans

private void detachOrCloseSpans(HttpServletRequest request,
			HttpServletResponse response, Span spanFromRequest, Throwable exception) {
		Span span = spanFromRequest;
		if (span != null) {
		    //添加response status到tags中
			addResponseTags(response, exception);
			//這里判斷span中savedSpan不為空且請求被TraceHandlerInterceptor攔截器攔截處理過則上報savedSpan信息
			//這里上報savedSpan,我的理解是traceFilter在filter一個request的時候會創(chuàng)建第一個parentSpan,
			//期間不會創(chuàng)建childSpan,但進(jìn)入springmvc handler處理期間可能會創(chuàng)建一些childSpan,然后設(shè)置為current span,
			//但這種span不是traceFilter關(guān)注的,它只關(guān)注server reciver時即剛接收到請求創(chuàng)建的span。
			if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) {
                //首先停止span的clock,即記錄結(jié)束時間,算下開始時間與結(jié)束時間的duration。
                //然后記錄server send event,表明作為server端,什么時候響應(yīng)請求返回結(jié)果的。
			    //最后上報span,比如上報到zipkin或者打印log,這就取決于初始化哪種spanReporter的了。
				recordParentSpan(span.getSavedSpan());
			} else if (!requestHasAlreadyBeenHandled(request)) {
			    //如果該請求沒有被TraceHandlerInterceptor攔截器攔截處理,則直接把span從當(dāng)前線程中移除,停止span的clock,然后上報
			    //這里的span可能是createSpanIfRequestNotHandled創(chuàng)建的span。
			    //close返回savedSpan,即parentSpan
				span = this.tracer.close(span);
			}
			//上報parentSpan
			recordParentSpan(span);
			// in case of a response with exception status will close the span when exception dispatch is handled
			// checking if tracing is in progress due to async / different order of view controller processing
			if (httpStatusSuccessful(response) && this.tracer.isTracing()) {
				if (log.isDebugEnabled()) {
					log.debug("Closing the span " + span + " since the response was successful");
				}
				this.tracer.close(span);
			} else if (errorAlreadyHandled(request) && this.tracer.isTracing()) {
				if (log.isDebugEnabled()) {
					log.debug(
							"Won't detach the span " + span + " since error has already been handled");
				}
			}  else if (shouldCloseSpan(request) && this.tracer.isTracing() && stillTracingCurrentSapn(span)) {
				if (log.isDebugEnabled()) {
					log.debug(
							"Will close span " + span + " since some component marked it for closure");
				}
				this.tracer.close(span);
			} else if (this.tracer.isTracing()) {
				if (log.isDebugEnabled()) {
					log.debug("Detaching the span " + span + " since the response was unsuccessful");
				}
				this.tracer.detach(span);
			}
		}
	}

以上就是springcloud-sleuth源碼怎么解析2-TraceFilter,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI