spring-cloud Sleuth的使用方法

 更新时间:2019年02月12日 11:15:17   作者:Degaulle  
这篇文章主要介绍了spring-cloud Sleuth的使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

 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:

  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:

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

看一下CurrentTraceContext的maybeScope:

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

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • dubbo服务链路跟踪方式

    dubbo服务链路跟踪方式

    这篇文章主要介绍了dubbo服务链路跟踪方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java手动配置线程池过程详解

    Java手动配置线程池过程详解

    这篇文章主要介绍了Java手动配置线程池过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • 详谈Map的key、value值的数据类型不能为基本类型的原因

    详谈Map的key、value值的数据类型不能为基本类型的原因

    这篇文章主要介绍了详谈Map的key、value值的数据类型不能为基本类型的原因,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • RabbitMQ交换机使用场景和消息可靠性总结分析

    RabbitMQ交换机使用场景和消息可靠性总结分析

    这篇文章主要为大家介绍了RabbitMQ交换机使用场景和消息可靠性总结分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • Springboot详解如何实现SQL注入过滤器过程

    Springboot详解如何实现SQL注入过滤器过程

    这篇文章主要介绍了基于springboot实现SQL注入过滤器,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2022-06-06
  • Java实现根据模板自动生成新的PPT

    Java实现根据模板自动生成新的PPT

    这篇文章主要介绍了如何利用Java代码自动生成PPT,具体就是查询数据库数据,然后根据模板文件(PPT),将数据库数据与模板文件(PPT),进行组合一下,生成新的PPT文件。感兴趣的可以了解一下
    2022-02-02
  • Java用Cookie限制点赞次数(简版)

    Java用Cookie限制点赞次数(简版)

    最近做了一个项目,其中有项目需求是,要用cookie实现限制点赞次数,特此整理,把实现代码分享给大家供大家学习
    2016-02-02
  • Java多线程Thread类的使用详解

    Java多线程Thread类的使用详解

    这篇文章主要介绍了Java多线程Thread类的使用及注意事项,在java标准库中提供了一个Thread类来表示/操作线程,Thread类也可以视为是java标准库提供的API
    2022-12-12
  • 5个步骤让你明白多线程和线程安全

    5个步骤让你明白多线程和线程安全

    本文详细讲解了多线程和线程安全的实现,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • Java程序中实现调用Python脚本的方法详解

    Java程序中实现调用Python脚本的方法详解

    这篇文章主要介绍了Java程序中实现调用Python脚本的方法,结合实例形式分析了eclipse环境中使用Java调用Python脚本的相关操作技巧与注意事项,需要的朋友可以参考下
    2018-03-03

最新评论