一直沒弄明白sleuth的tracercontext是如何創建和傳遞的,閑來無事研究了一下。由于對sleuth的源碼不熟悉,準備通過debug brave.tracer的nextid()方法,查看方法調用棧來找來龍去脈。
首先創建兩個service a和b,記作srva、srvb,在srva中添加testa controller,sevb中添加testb controller,testa中通過feign調用testb。
先看當用戶通過瀏覽器調用srva的時候,srva是作為server的。
configuration:
tracewebservletautoconfiguration==>tracingfilter
tracehttpautoconfiguration==>httptracing
traceautoconfiguration==>tracing
sleuthlogautoconfiguration.slf4jconfiguration==>currenttracecontext
配置中,tracingfilter在實例化時需要一個httptracing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public static filter create(httptracing httptracing) { return new tracingfilter(httptracing); } //servlet運行時類 final servletruntime servlet = servletruntime.get(); //slf4jcurrenttracecontext final currenttracecontext currenttracecontext; final tracer tracer; final httpserverhandler<httpservletrequest, httpservletresponse> handler; //tracecontext的數據提取器 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模式構造時接收一個tracing:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
tracing tracing; //客戶端span解析器 httpclientparser clientparser; string servername; //服務端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實例化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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; //靜態方法create,local對象為threadlocal類型 /** uses a non-inheritable static thread local */ public static currenttracecontext create() { return new default ( default ); } //local對象為inheritablethreadlocal類型 //官方文檔指出,inheritable方法在線程池的環境中需謹慎使用,可能會取出錯誤的tracecontext,這樣會導致span等信息會記錄并關聯到錯誤的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(); } //替換當前tracecontext,close方法將之前的tracecontext設置回去 //scope接口繼承了closeable接口,在try中使用會自動調用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使用的就是一個inheritablethreadlocal,inheritablethreadlocal在創建子線程的時候,會將父線程的inheritablethreadlocals繼承下來。這樣就實現了tracecontext在父子線程中的傳遞。
看一下currenttracecontext的maybescope:
1
2
3
4
5
6
7
8
9
10
11
|
//返回一個新的scope,如果當前scope就是傳入的scope,返回一個空scope public scope maybescope( @nullable tracecontext currentspan) { //獲取當前tracecontext tracecontext currentscope = get(); //如果傳入的tracecontext為空,且當前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:請輸入代碼
2.srva請求到servb時作為client。
traceloadbalancerfeignclient-->loadbalancerfeignclient-->feignloadbalancer-->lazytracingfeignclient-->client
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000018115247