zoukankan      html  css  js  c++  java
  • spring cloud sleuth在spring中创建span

    在spring-cloud-sleuth的META-INF里的spring.factories里设置了一下:

    org.springframework.boot.env.EnvironmentPostProcessor=
    org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor
     

    这样,TraceEnvironmentPostProcessor被配置在了ioc容器初始化之前。spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.TraceEnvironmentPostProcessor.java

    public class TraceEnvironmentPostProcessor implements EnvironmentPostProcessor {
    
        private static final String PROPERTY_SOURCE_NAME = "defaultProperties";
        private static final String SPRING_AOP_PROXY_TARGET_CLASS = "spring.aop.proxyTargetClass";
    
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment,
                SpringApplication application) {
            Map<String, Object> map = new HashMap<String, Object>();
            // This doesn't work with all logging systems but it's a useful default so you see
            // traces in logs without having to configure it.
            if (Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true"))) {
                map.put("logging.pattern.level",
                        "%5p [${spring.zipkin.service.name:${spring.application.name:-}},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]");
            }
            // TODO: Remove this in 2.0.x. For compatibility we always set to true
            if (!environment.containsProperty(SPRING_AOP_PROXY_TARGET_CLASS)) {
                map.put(SPRING_AOP_PROXY_TARGET_CLASS, "true");
            }
            addOrReplace(environment.getPropertySources(), map);
        }
        //...
    }

    在TraceEnvironmentPostProcessor的postProcessEnvironment()方法里保证了两件事情:

    1、设置了spring.aop.proxyTargetClass参数为true保证了cglib代理的开启,并加入了日志的追踪打印的模板。
    2、而后在配置类TraceAutoConfiguration中生成了Tracer,默认实现为DefaultTraces,作用为正式创建一个工作单元span。

    紧随其后的配置类为SleuthAnnotationAutoConfiguration,见spring-cloud-sleuth-core包的org.springframework.cloud.sleuth.annotation.SleuthAnnotationAutoConfiguration.java

    @Configuration
    @ConditionalOnBean(Tracer.class)
    @ConditionalOnProperty(name = "spring.sleuth.annotation.enabled", matchIfMissing = true)
    @AutoConfigureAfter(TraceAutoConfiguration.class)
    @EnableConfigurationProperties(SleuthAnnotationProperties.class)
    public class SleuthAnnotationAutoConfiguration {
        
        @Bean
        @ConditionalOnMissingBean
        SpanCreator spanCreator(Tracer tracer) {
            return new DefaultSpanCreator(tracer);
        }
        //...
        @Bean
        SleuthAdvisorConfig sleuthAdvisorConfig() {
            return new SleuthAdvisorConfig();
        }
    }

    此处根据之前生成的Tracer进一步创建了其包装类SpanCreator,并最重要的是生成了代理的配置类SleuthAdvisorConfig。

    class SleuthAdvisorConfig  extends AbstractPointcutAdvisor implements BeanFactoryAware {
    
        private Advice advice;
    
        private Pointcut pointcut;
    
        private BeanFactory beanFactory;
    
        @PostConstruct
        public void init() {
            this.pointcut = buildPointcut();
            this.advice = buildAdvice();
            if (this.advice instanceof BeanFactoryAware) {
                ((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
            }
        }
        //...
    }

    其初始化方法中,完成了切面与切面增强的创建。
    其buildPointcut()方法:

    private Pointcut buildPointcut() {
            return new AnnotationClassOrMethodOrArgsPointcut();
        }
    
        /**
         * Checks if a class or a method is is annotated with Sleuth related annotations
         */
        private final class AnnotationClassOrMethodOrArgsPointcut extends
                DynamicMethodMatcherPointcut {
    
            @Override
            public boolean matches(Method method, Class<?> targetClass, Object... args) {
                return getClassFilter().matches(targetClass);
            }
    
            @Override public ClassFilter getClassFilter() {
                return new ClassFilter() {
                    @Override public boolean matches(Class<?> clazz) {
                        return new AnnotationClassOrMethodFilter(NewSpan.class).matches(clazz) ||
                                new AnnotationClassOrMethodFilter(ContinueSpan.class).matches(clazz);
                    }
                };
            }
    
        }
    
        private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
    
            private final AnnotationMethodsResolver methodResolver;
    
            AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
                super(annotationType, true);
                this.methodResolver = new AnnotationMethodsResolver(annotationType);
            }
    
            @Override
            public boolean matches(Class<?> clazz) {
                return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
            }
    
        }

    显而易见,切面的匹配通过目标类是否满足使用了NewSpan或者ContinueSpan注解。
    切面的增强则通过buildAdvice来构造Interceptor来通过代理使用invoke()方法来完成生成span的目的。

        private Advice buildAdvice() {
            return new SleuthInterceptor();
        }
    class SleuthInterceptor  implements IntroductionInterceptor, BeanFactoryAware  {
    
        private static final Log logger = LogFactory.getLog(MethodHandles.lookup().lookupClass());
        private static final String CLASS_KEY = "class";
        private static final String METHOD_KEY = "method";
    
        private BeanFactory beanFactory;
        private SpanCreator spanCreator;
        private Tracer tracer;
        private SpanTagAnnotationHandler spanTagAnnotationHandler;
        private ErrorParser errorParser;
    
        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            Method method = invocation.getMethod();
            if (method == null) {
                return invocation.proceed();
            }
            Method mostSpecificMethod = AopUtils
                    .getMostSpecificMethod(method, invocation.getThis().getClass());
            NewSpan newSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, NewSpan.class);
            ContinueSpan continueSpan = SleuthAnnotationUtils.findAnnotation(mostSpecificMethod, ContinueSpan.class);
            if (newSpan == null && continueSpan == null) {
                return invocation.proceed();
            }
            Span span = tracer().getCurrentSpan();
            String log = log(continueSpan);
            boolean hasLog = StringUtils.hasText(log);
            try {
                if (newSpan != null) {
                    span = spanCreator().createSpan(invocation, newSpan);
                }
                if (hasLog) {
                    logEvent(span, log + ".before");
                }
                spanTagAnnotationHandler().addAnnotatedParameters(invocation);
                addTags(invocation, span);
                return invocation.proceed();
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Exception occurred while trying to continue the pointcut", e);
                }
                if (hasLog) {
                    logEvent(span, log + ".afterFailure");
                }
                errorParser().parseErrorTags(tracer().getCurrentSpan(), e);
                throw e;
            } finally {
                if (span != null) {
                    if (hasLog) {
                        logEvent(span, log + ".after");
                    }
                    if (newSpan != null) {
                        tracer().close(span);
                    }
                }
            }
        }
        //...
    }

    当通过代理走到此处的invoke()方法说明此时涉及到了与别的服务的调用,需要生成新的spanId,那么就在这里newSpan注解生成新的spanId,如果该方法实现了ContinueSpan注解,那么就在现有的spanId。
    如果采用newSpan注解,那么这里需要通过之前的在配置类中生成的tracer的getCurrentSpan()方法获取当前的span。
    具体的实现在SpanContextHolder的getCurrentSpan()方法中。

    class SpanContextHolder {
    
        private static final Log log = org.apache.commons.logging.LogFactory
                .getLog(SpanContextHolder.class);
        private static final ThreadLocal<SpanContext> CURRENT_SPAN = new NamedThreadLocal<>(
                "Trace Context");
    
        /**
         * Get the current span out of the thread context
         */
        static Span getCurrentSpan() {
            return isTracing() ? CURRENT_SPAN.get().span : null;
        }

    通过ThreadLoacl来获得当前的span,也就是说,当新的trace请求到来时,可以通过ThreadLoacl来存储。
    紧接着通过spanCreator的createSpan()方法来证实获得新的span。

    @Override public Span createSpan(MethodInvocation pjp, NewSpan newSpanAnnotation) {
       String name = StringUtils.isEmpty(newSpanAnnotation.name()) ?
             pjp.getMethod().getName() : newSpanAnnotation.name();
       String changedName = SpanNameUtil.toLowerHyphen(name);
       if (log.isDebugEnabled()) {
          log.debug("For the class [" + pjp.getThis().getClass() + "] method "
                + "[" + pjp.getMethod().getName() + "] will name the span [" + changedName + "]");
       }
       return createSpan(changedName);
    }
     
    private Span createSpan(String name) {
       if (this.tracer.isTracing()) {
          return this.tracer.createSpan(name, this.tracer.getCurrentSpan());
       }
       return this.tracer.createSpan(name);
    }

    Span的名字在注解中的名字和方法名中有限选择前者,而后根据通过tracer的createSpan()来获得span。

    @Override
    public Span createSpan(String name, Span parent) {
       if (parent == null) {
          return createSpan(name);
       }
       return continueSpan(createChild(parent, name));
    }

    如果此时没有任何span存在,那么直接通过createSpan().

    @Override
    public Span createSpan(String name) {
       return this.createSpan(name, this.defaultSampler);
    }
     
    @Override
    public Span createSpan(String name, Sampler sampler) {
       String shortenedName = SpanNameUtil.shorten(name);
       Span span;
       if (isTracing()) {
          span = createChild(getCurrentSpan(), shortenedName);
       }
       else {
          long id = createId();
          span = Span.builder().name(shortenedName)
                .traceIdHigh(this.traceId128 ? createTraceIdHigh() : 0L)
                .traceId(id)
                .spanId(id).build();
          if (sampler == null) {
             sampler = this.defaultSampler;
          }
          span = sampledSpan(span, sampler);
          this.spanLogger.logStartedSpan(null, span);
       }
       return continueSpan(span);
    }

    这里可以看到spanId的构造,如果当时是首次构建spanId,那么首先会创建一个traceId,作为本次跟踪流 的id。并与第一次的spanID相同。

    但是,此时若是已经存在span,也就是说这并不是第一次,那么就没有必要将traceId设为该次创建的spanId,而是在createChild()方法中,记录当前的traceId为原来收到的traceId,并将收到的spanId作为parentId,并将savedSpan指向原来的span,重新生成一个spanId,并将新的span作为当前的span。

    在完成了span的创建后,则会经过sample的判断,此次是否要使用span记录,可以根据配置修改sample的类型,如果采用了百分比类型的,那么可能不会记录下来,完全复制一份span,但是把其exportable属性改为false。

  • 相关阅读:
    802.11协议网文转载
    三角函数公式大全
    IEEE 802.11ac Standard
    FPGA设计如何进行面积优化
    直流偏差消除
    I/Q不平衡补偿
    WiFi6剩余频偏的影响
    SQL SERVER 调优等待类型
    Nginx
    fatal: HttpRequestException encountered解决方法
  • 原文地址:https://www.cnblogs.com/duanxz/p/6796576.html
Copyright © 2011-2022 走看看