一直没弄明白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测试数据/a/1190000018115247
查看更多关于spring-cloud Sleuth的使用方法的详细内容...