spring-cloud Sleuth

栏目: Java · 发布时间: 6年前

内容简介:一直没弄明白sleuth的tracerContext是如何创建和传递的,闲来无事研究了一下。由于对sleuth的源码不熟悉,准备通过debug首先创建两个service A和B,记作srvA、srvB,在srvA中添加testA controller,sevB中添加testB controller,testA中通过Feign调用testB。

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

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

spring-cloud Sleuth

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

configuration :

TraceWebServletAutoConfiguration==> TracingFilter

TraceHttpAutoConfiguration==> HttpTracing

TraceAutoConfiguration==> Tracing

SleuthLogAutoConfiguration.Slf4jConfiguration==> CurrentTraceContext

spring-cloud Sleuth

配置中, 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<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);
  }

HttpTracingBuilder模式构造时接收一个 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<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:

@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<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使用的就是一个 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


以上所述就是小编给大家介绍的《spring-cloud Sleuth》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Lean Startup

The Lean Startup

Eric Ries / Crown Business / 2011-9-13 / USD 26.00

更多中文介绍:http://huing.com Most startups fail. But many of those failures are preventable. The Lean Startup is a new approach being adopted across the globe, chan ging the way companies are built and ......一起来看看 《The Lean Startup》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具