spring-cloud Sleuth的使用方法
一直沒(méi)弄明白sleuth的tracerContext是如何創(chuàng)建和傳遞的,閑來(lái)無(wú)事研究了一下。由于對(duì)sleuth的源碼不熟悉,準(zhǔn)備通過(guò)debug brave.Tracer的nextId()方法,查看方法調(diào)用棧來(lái)找來(lái)龍去脈。
首先創(chuàng)建兩個(gè)service A和B,記作srvA、srvB,在srvA中添加testA controller,sevB中添加testB controller,testA中通過(guò)Feign調(diào)用testB。
先看當(dāng)用戶通過(guò)瀏覽器調(diào)用srvA的時(shí)候,srvA是作為server的。
configuration:
TraceWebServletAutoConfiguration==>TracingFilter
TraceHttpAutoConfiguration==>HttpTracing
TraceAutoConfiguration==>Tracing
SleuthLogAutoConfiguration.Slf4jConfiguration==>CurrentTraceContext
配置中,TracingFilter在實(shí)例化時(shí)需要一個(gè)HttpTracing:
public static Filter create(HttpTracing httpTracing) { return new TracingFilter(httpTracing); } //Servlet運(yùn)行時(shí)類 final ServletRuntime servlet = ServletRuntime.get(); //Slf4jCurrentTraceContext final CurrentTraceContext currentTraceContext; final Tracer tracer; final HttpServerHandler<HttpServletRequest, HttpServletResponse> handler; //TraceContext的數(shù)據(jù)提取器 final TraceContext.Extractor<HttpServletRequest> extractor; TracingFilter(HttpTracing httpTracing) { tracer = httpTracing.tracing().tracer(); currentTraceContext = httpTracing.tracing().currentTraceContext(); handler = HttpServerHandler.create(httpTracing, ADAPTER); extractor = httpTracing.tracing().propagation().extractor(GETTER); }
HttpTracing Builder模式構(gòu)造時(shí)接收一個(gè)Tracing:
Tracing tracing; //客戶端span解析器 HttpClientParser clientParser; String serverName; //服務(wù)端span解析器 HttpServerParser serverParser; HttpSampler clientSampler, serverSampler; Builder(Tracing tracing) { if (tracing == null) throw new NullPointerException("tracing == null"); final ErrorParser errorParser = tracing.errorParser(); this.tracing = tracing; this.serverName = ""; // override to re-use any custom error parser from the tracing component this.clientParser = new HttpClientParser() { @Override protected ErrorParser errorParser() { return errorParser; } }; this.serverParser = new HttpServerParser() { @Override protected ErrorParser errorParser() { return errorParser; } }; this.clientSampler = HttpSampler.TRACE_ID; this.serverSampler(HttpSampler.TRACE_ID); }
Tracing實(shí)例化:
@Bean @ConditionalOnMissingBean // NOTE: stable bean name as might be used outside sleuth Tracing tracing(@Value("${spring.zipkin.service.name:${spring.application.name:default}}") String serviceName, Propagation.Factory factory, CurrentTraceContext currentTraceContext, Reporter<zipkin2.Span> reporter, Sampler sampler, ErrorParser errorParser, SleuthProperties sleuthProperties ) { return Tracing.newBuilder() .sampler(sampler) .errorParser(errorParser) .localServiceName(serviceName) //ExtraFieldPropagation.Factory .propagationFactory(factory) .currentTraceContext(currentTraceContext) .spanReporter(adjustedReporter(reporter)) .traceId128Bit(sleuthProperties.isTraceId128()) .supportsJoin(sleuthProperties.isSupportsJoin()) .build(); }
下面看TracingFilter的doFilter:
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()); Throwable error = null; Scope scope = currentTraceContext.newScope(span.context()); try { // any downstream code can see Tracer.currentSpan() or use Tracer.currentSpanCustomizer() chain.doFilter(httpRequest, httpResponse); } catch (IOException | ServletException | RuntimeException | Error e) { error = e; throw e; } finally { scope.close(); if (servlet.isAsync(httpRequest)) { // we don't have the actual response, handle later servlet.handleAsync(handler, httpRequest, httpResponse, span); } else { // we have a synchronous response, so we can finish the span handler.handleSend(ADAPTER.adaptResponse(httpRequest, httpResponse), error, span); } } }
在SleuthLogAutoConfiguration中如果有slfj的包,則注入CurrentTraceContext:
@Configuration @ConditionalOnClass(MDC.class) @EnableConfigurationProperties(SleuthSlf4jProperties.class) protected static class Slf4jConfiguration { @Bean @ConditionalOnProperty(value = "spring.sleuth.log.slf4j.enabled", matchIfMissing = true) @ConditionalOnMissingBean public CurrentTraceContext slf4jSpanLogger() { return Slf4jCurrentTraceContext.create(); } ... }
Slf4jCurrentTraceContext中,delegate就是CurrentTraceContext.Default.inheritable():
public static final class Default extends CurrentTraceContext { static final ThreadLocal<TraceContext> DEFAULT = new ThreadLocal<>(); // Inheritable as Brave 3's ThreadLocalServerClientAndLocalSpanState was inheritable static final InheritableThreadLocal<TraceContext> INHERITABLE = new InheritableThreadLocal<>(); final ThreadLocal<TraceContext> local; //靜態(tài)方法create,local對(duì)象為T(mén)hreadLocal類型 /** Uses a non-inheritable static thread local */ public static CurrentTraceContext create() { return new Default(DEFAULT); } //local對(duì)象為InheritableThreadLocal類型 //官方文檔指出,inheritable方法在線程池的環(huán)境中需謹(jǐn)慎使用,可能會(huì)取出錯(cuò)誤的TraceContext,這樣會(huì)導(dǎo)致Span等信息會(huì)記錄并關(guān)聯(lián)到錯(cuò)誤的traceId上 /** * Uses an inheritable static thread local which allows arbitrary calls to {@link * Thread#start()} to automatically inherit this context. This feature is available as it is was * the default in Brave 3, because some users couldn't control threads in their applications. * * <p>This can be a problem in scenarios such as thread pool expansion, leading to data being * recorded in the wrong span, or spans with the wrong parent. If you are impacted by this, * switch to {@link #create()}. */ public static CurrentTraceContext inheritable() { return new Default(INHERITABLE); } Default(ThreadLocal<TraceContext> local) { if (local == null) throw new NullPointerException("local == null"); this.local = local; } @Override public TraceContext get() { return local.get(); } //替換當(dāng)前TraceContext,close方法將之前的TraceContext設(shè)置回去 //Scope接口繼承了Closeable接口,在try中使用會(huì)自動(dòng)調(diào)用close方法,為了避免用戶忘記close方法,還提供了Runnable,Callable,Executor,ExecutorService包裝方法 @Override public Scope newScope(@Nullable TraceContext currentSpan) { final TraceContext previous = local.get(); local.set(currentSpan); class DefaultCurrentTraceContextScope implements Scope { @Override public void close() { local.set(previous); } } return new DefaultCurrentTraceContextScope(); } }
Slf4jCurrentTraceContext的delegate使用的就是一個(gè)InheritableThreadLocal,InheritableThreadLocal在創(chuàng)建子線程的時(shí)候,會(huì)將父線程的inheritableThreadLocals繼承下來(lái)。這樣就實(shí)現(xiàn)了TraceContext在父子線程中的傳遞。
看一下CurrentTraceContext的maybeScope:
//返回一個(gè)新的scope,如果當(dāng)前scope就是傳入的scope,返回一個(gè)空scope public Scope maybeScope(@Nullable TraceContext currentSpan) { //獲取當(dāng)前TraceContext TraceContext currentScope = get(); //如果傳入的TraceContext為空,且當(dāng)前TraceContext為空返回空scope if (currentSpan == null) { if (currentScope == null) return Scope.NOOP; return newScope(null); } return currentSpan.equals(currentScope) ? Scope.NOOP : newScope(currentSpan); }
TracingFilter中HttpServerHandler解析Request:請(qǐng)輸入代碼
2.srvA請(qǐng)求到servB時(shí)作為Client。
TraceLoadBalancerFeignClient-->LoadBalancerFeignClient-->FeignLoadBalancer-->LazyTracingFeignClient-->Client
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳談Map的key、value值的數(shù)據(jù)類型不能為基本類型的原因
這篇文章主要介紹了詳談Map的key、value值的數(shù)據(jù)類型不能為基本類型的原因,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09RabbitMQ交換機(jī)使用場(chǎng)景和消息可靠性總結(jié)分析
這篇文章主要為大家介紹了RabbitMQ交換機(jī)使用場(chǎng)景和消息可靠性總結(jié)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Springboot詳解如何實(shí)現(xiàn)SQL注入過(guò)濾器過(guò)程
這篇文章主要介紹了基于springboot實(shí)現(xiàn)SQL注入過(guò)濾器,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2022-06-06Java實(shí)現(xiàn)根據(jù)模板自動(dòng)生成新的PPT
這篇文章主要介紹了如何利用Java代碼自動(dòng)生成PPT,具體就是查詢數(shù)據(jù)庫(kù)數(shù)據(jù),然后根據(jù)模板文件(PPT),將數(shù)據(jù)庫(kù)數(shù)據(jù)與模板文件(PPT),進(jìn)行組合一下,生成新的PPT文件。感興趣的可以了解一下2022-02-02Java用Cookie限制點(diǎn)贊次數(shù)(簡(jiǎn)版)
最近做了一個(gè)項(xiàng)目,其中有項(xiàng)目需求是,要用cookie實(shí)現(xiàn)限制點(diǎn)贊次數(shù),特此整理,把實(shí)現(xiàn)代碼分享給大家供大家學(xué)習(xí)2016-02-02Java程序中實(shí)現(xiàn)調(diào)用Python腳本的方法詳解
這篇文章主要介紹了Java程序中實(shí)現(xiàn)調(diào)用Python腳本的方法,結(jié)合實(shí)例形式分析了eclipse環(huán)境中使用Java調(diào)用Python腳本的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-03-03