博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring-cloud Sleuth
阅读量:6159 次
发布时间:2019-06-21

本文共 8277 字,大约阅读时间需要 27 分钟。

一直没弄明白sleuth的tracerContext是如何创建和传递的,闲来无事研究了一下。由于对sleuth的源码不熟悉,准备通过debug brave.Tracer的nextId()方法,查看方法调用栈来找来龙去脉。

首先创建两个service A和B,记作srvA、srvB,在srvA中添加testA controller,sevB中添加testB controller,testA中通过Feign调用testB。

clipboard.png

  1. 先看当用户通过浏览器调用srvA的时候,srvA是作为server的。

configuration:

TraceWebServletAutoConfiguration==>TracingFilter
TraceHttpAutoConfiguration==>HttpTracing
TraceAutoConfiguration==>Tracing
SleuthLogAutoConfiguration.Slf4jConfiguration==>CurrentTraceContext

clipboard.png

配置中,TracingFilter在实例化时需要一个HttpTracing

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
handler; //TraceContext的数据提取器 final TraceContext.Extractor
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

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实例化:

@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
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:

@Override  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)      throws IOException, ServletException {    HttpServletRequest httpRequest = (HttpServletRequest) request;    HttpServletResponse httpResponse = servlet.httpResponse(response);    // Prevent duplicate spans for the same request    TraceContext context = (TraceContext) request.getAttribute(TraceContext.class.getName());    if (context != null) {      // A forwarded request might end up on another thread, so make sure it is scoped      Scope scope = currentTraceContext.maybeScope(context);      try {        chain.doFilter(request, response);      } finally {        scope.close();      }      return;    }    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
DEFAULT = new ThreadLocal<>(); // Inheritable as Brave 3's ThreadLocalServerClientAndLocalSpanState was inheritable static final InheritableThreadLocal
INHERITABLE = new InheritableThreadLocal<>(); final ThreadLocal
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. * *

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

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使用的就是一个InheritableThreadLocalInheritableThreadLocal在创建子线程的时候,会将父线程的inheritableThreadLocals继承下来。这样就实现了TraceContext在父子线程中的传递。

看一下CurrentTraceContextmaybeScope

//返回一个新的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

转载地址:http://lyofa.baihongyu.com/

你可能感兴趣的文章
分析jQuery源码时记录的一点感悟
查看>>
程序局部性原理感悟
查看>>
UIView 动画进阶
查看>>
ROS机器人程序设计(原书第2版)1.4.7 在BeagleBone Black中安装rosinstall
查看>>
Spring如何处理线程并发
查看>>
linux常用命令(用户篇)
查看>>
获取组件的方式(方法)
查看>>
win2008 server_R2 自动关机 解决
查看>>
我的友情链接
查看>>
在C#调用C++的DLL简析(二)—— 生成托管dll
查看>>
Linux macos 常用终端操作
查看>>
企业网络的管理思路
查看>>
Linux磁盘分区与挂载
查看>>
J2se学习笔记一
查看>>
DNS视图及日志系统
查看>>
老李分享:Android性能优化之内存泄漏 3
查看>>
mysql命令
查看>>
来自极客标签10款最新设计素材-系列七
查看>>
极客技术专题【009期】:web技术开发小技巧
查看>>
PHP 简单计算器代码实现
查看>>