您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關springcloud-sleuth源碼怎么解析2-TraceFilter,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
基于spring cloud 1.2.1版本
將分析server接收一個請求,trace究竟是怎么處理的。
首先介紹下一個span的生命周期:
start
創建一個span,這時候會記錄創建時間以及設置span name。如果當前線程已經存在一個span,則創建的新的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已經準備好將自身發送到zipkin server或者其他collect的時候,便會調用close方法,記錄endTime,上報span,然后從當前線程中移除span。
continue
將一個新的span設置到當前線程中,成為continueSpan。該方法作用是傳遞不同線程之間的span。對于一些異步處理代碼,就需要 將span設置到異步處理線程中了。
Span continuedSpan = this.tracer.continueSpan(spanToContinue);
detach
從當前線程中刪除span,剝離出去,但不會stop或者close span。一般跟continue方法結合使用。
// 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
創建一個span并且指定它的parent span。該方法的使用場景是在當前線程中想創建一個span,但parent span存在另一個線程當中,這樣你 就可以獲取到parent span,明確指定該span為要創建的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方法。
@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。根據兩個條件判斷是否忽略本次trace: //A、根據skipPattern判斷此uri是否是skip uri,如果是返回true //B、從request、response的head中獲取X-B3-Sampled屬性,如果值為0則返回true,即不進行采樣 boolean skip = this.skipPattern.matcher(uri).matches() || Span.SPAN_NOT_SAMPLED.equals(ServletUtils.getHeader(request, response, Span.SAMPLED_NAME)); //從request中getAttribute span。表示在一個request中,如果發生了轉發那直接可以在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 //正如上面的注釋所說,這是請求出現異常時,跳轉到異常controller時處理邏輯,然后關閉span立即結束filter。 if (!httpStatusSuccessful(response) && isSpanContinued(request)) { Span parentSpan = parentSpan(spanFromRequest); processErrorRequest(filterChain, request, new TraceHttpServletResponse(response, parentSpan), spanFromRequest); return; } //設置span name String name = HTTP_COMPONENT + ":" + uri; Throwable exception = null; try { //根據request創建span,下面分析createSpan代碼。 spanFromRequest = createSpan(request, skip, spanFromRequest, name); //這里會觸發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則不進行處理 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, //彌補springmvc的trace攔截器缺失的部分,這樣能保證對于zipkin來說是一個合理的調用鏈路。 spanFromRequest = createSpanIfRequestNotHandled(request, spanFromRequest, name, skip); //分離獲取關閉span,最后來分析下該方法 detachOrCloseSpans(request, response, spanFromRequest, exception); } }
private void continueSpan(HttpServletRequest request, Span spanFromRequest) { //tracer的continueSpan方法的作用是將新的span設置到當前線程中。 //比如span a 在線程X中,span b在線程Y中,現在上下文處于線程b中,然后操作continueSpan(a), //即將線程Y中的span b替換成span a,然后span a中的saved span屬性設置成span b,即設置當前線程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); } }
public Span continueSpan(Span span) { if (span != null) { //日志組件,主要用于MDC輸出的 this.spanLogger.logContinuedSpan(span); } else { return null; } //createContinuedSpan方法第一個參數span是request中保存的span,或者其他上下文傳遞下來的。 //第二個span,SpanContextHolder.getCurrentSpan()是從ThreadLocal獲取當前線程中的span。 //下面看下createContinuedSpan方法 Span newSpan = createContinuedSpan(span, SpanContextHolder.getCurrentSpan()); //將新的span保存到當前線程中 SpanContextHolder.setCurrentSpan(newSpan); return newSpan; } //如果當前線程span為空且被傳遞過來的span的saved span屬性不為空,則設置新的saved span為被傳遞過來的span的saved span, //否則saved span使用當前線程中的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)這種構造span的形式我們來分析下saved span有何作用:
saved的span是在創建該新的span之前就已經存在當前線程中的span。有兩種情況會調用該api:
正如之前所說的,span從線程X復制到線程Y中
當前線程中已有span,然后創建child span,child span的saved span就是parent span
/** * Creates a span and appends it as the current request's attribute */ private Span createSpan(HttpServletRequest request, boolean skip, Span spanFromRequest, String name) { //如果spanFromRequest不為空,即發生了轉發等情況,那直接返回,不需要創建新的span。 if (spanFromRequest != null) { if (log.isDebugEnabled()) { log.debug("Span has already been created - continuing with the previous one"); } return spanFromRequest; } //抽取request中的header、path等信息創建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信息,導致span信息不完整, //那么就需要在server端生成tags。 addRequestTagsForParentSpan(request, parent); spanFromRequest = parent; //將生成的span保存到當前線程中,詳情看DefaultTracer.continueSpan方法,前面已分析。 this.tracer.continueSpan(spanFromRequest); //判斷該span是否跨進程,是的話會加SR標識,即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,則會生成一個不采樣標識的span if (skip) { spanFromRequest = this.tracer.createSpan(name, NeverSampler.INSTANCE); } else { //根據request header中的采樣標識判斷直接采樣,還是根據本地設置的采樣器判斷是否采樣 //下面分析下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; }
public Span joinTrace(SpanTextMap textMap) { //carrier中保存request header、uri信息 Map<String, String> carrier = TextMapUtil.asMap(textMap); //判斷header中是否有Span.SPAN_FLAGS標識,且值為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中提取信息構建span return null; } try { String uri = carrier.get(URI_HEADER); //根據uri判斷是夠skip boolean skip = this.skipPattern.matcher(uri).matches() || Span.SPAN_NOT_SAMPLED.equals(carrier.get(Span.SAMPLED_NAME)); //如果header中span id為空則根據trace id生成一個,否則直接返回header中的span id。 long spanId = spanId(carrier); //構建span return buildParentSpan(carrier, uri, skip, spanId); } catch (Exception e) { log.error("Exception occurred while trying to extract span from carrier", e); return null; } }
public Span createSpan(String name, Sampler sampler) { String shortenedName = SpanNameUtil.shorten(name); Span span; //如果本地即當前線程已經存在span,則創建child span,當前線程中的span為parent span //如果不存在span,則創建一個完全新的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); } //將創建的span保存在當前線程 return continueSpan(span); }
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的時候會創建第一個parentSpan, //期間不會創建childSpan,但進入springmvc handler處理期間可能會創建一些childSpan,然后設置為current span, //但這種span不是traceFilter關注的,它只關注server reciver時即剛接收到請求創建的span。 if (span.hasSavedSpan() && requestHasAlreadyBeenHandled(request)) { //首先停止span的clock,即記錄結束時間,算下開始時間與結束時間的duration。 //然后記錄server send event,表明作為server端,什么時候響應請求返回結果的。 //最后上報span,比如上報到zipkin或者打印log,這就取決于初始化哪種spanReporter的了。 recordParentSpan(span.getSavedSpan()); } else if (!requestHasAlreadyBeenHandled(request)) { //如果該請求沒有被TraceHandlerInterceptor攔截器攔截處理,則直接把span從當前線程中移除,停止span的clock,然后上報 //這里的span可能是createSpanIfRequestNotHandled創建的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,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。