一直没弄明白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