spring-cloud Sleuth的使用方法
一直沒弄明白sleuth的tracerContext是如何創(chuàng)建和傳遞的,閑來無事研究了一下。由于對(duì)sleuth的源碼不熟悉,準(zhǔn)備通過debug brave.Tracer的nextId()方法,查看方法調(diào)用棧來找來龍去脈。
首先創(chuàng)建兩個(gè)service A和B,記作srvA、srvB,在srvA中添加testA controller,sevB中添加testB controller,testA中通過Feign調(diào)用testB。

先看當(dāng)用戶通過瀏覽器調(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ì)象為ThreadLocal類型
/** 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繼承下來。這樣就實(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)文章
SpringBoot使用spring retry重試機(jī)制的操作詳解
實(shí)際工作中由于網(wǎng)絡(luò)波動(dòng)等原因?qū)е麓a執(zhí)行失敗需要重新執(zhí)行,保證最終能夠完成業(yè)務(wù)功能,通常來說,會(huì)用try/catch,while循環(huán)或者定時(shí)任務(wù)重處理,但是這樣的做法缺乏統(tǒng)一性,要多寫很多代碼,spring-retry組件可以通過注解優(yōu)雅的實(shí)現(xiàn)重處理功能2024-12-12
String實(shí)例化及static final修飾符實(shí)現(xiàn)方法解析
這篇文章主要介紹了String實(shí)例化及static final修飾符實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
詳解Java8新特性之interface中的static方法和default方法
這篇文章主要介紹了Java8新特性之interface中的static方法和default方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-08-08
java實(shí)現(xiàn)將ftp和http的文件直接傳送到hdfs
前面幾篇文章,我們已經(jīng)做了很好的鋪墊了,幾個(gè)要用到的工具我們都做了出來,本文就是將他們集合起來,說下具體的用法,小伙伴們可以參考下。2015-03-03
Java中的clone方法詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
clone顧名思義就是復(fù)制, 在Java語言中, clone方法被對(duì)象調(diào)用,所以會(huì)復(fù)制對(duì)象。下面通過本文給大家介紹java中的clone方法,感興趣的朋友一起看看吧2017-06-06
Spring boot redis cache的key的使用方法
這篇文章主要介紹了Spring boot redis cache的key的使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
Java對(duì)象比較之equals與hashCode詳解
這篇文章主要介紹了Java對(duì)象比較之equals與hashCode詳解,equals?方法和?hashCode?方法是?Object?類中的兩個(gè)基礎(chǔ)方法,它們共同協(xié)作來判斷兩個(gè)對(duì)象是否相等,需要的朋友可以參考下2023-12-12
Java數(shù)據(jù)結(jié)構(gòu)之LinkedList的用法詳解
鏈表(Linked?list)是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線性表。Java的LinkedList(鏈表)?類似于?ArrayList,是一種常用的數(shù)據(jù)容器,本文就來簡(jiǎn)單講講它的使用吧2023-05-05

