Spring Cloud学习——链路追踪:sleuth

为什么需要Spring Cloud Sleuth

微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。

Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,你只需要在pom文件中引入相应的依赖即可。

术语

spring cloud sleuth沿用Google的Dapper的术语:

  • trace: 是由众多span组成的树形结构,使用64位标识生成唯一调度ID。
  • span: 基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)
    span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。
  • Annotation: 用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束:
    • cs: Client Sent -客户端发起一个请求,这个annotation描述了这个span的开始。
    • sr: Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟。
    • ss: Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间。
    • cr: Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间。
      在这里插入图片描述

源码解析

spring-cloud-sleuth-core 结构:
在这里插入图片描述

  • 可以看到源码中支持的追踪类型有很多,支持async,hystrix,websocket,rxjava,Spring mvc,servlet,spring restTemplate,feign,zuul等等,这里我着重探讨spring web mvc的链路追踪。
  • 打开web包,找到TraceWebServletAutoConfiguration,这里配置了主要的初始化类。

在这里插入图片描述

过滤器注册

当启动初始化程序时,跟踪TraceWebServletAutoConfiguration代码如下

    @Bean
    public FilterRegistrationBean traceWebFilter(TracingFilter tracingFilter) {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(tracingFilter, new ServletRegistrationBean[0]);
        filterRegistrationBean.setDispatcherTypes(DispatcherType.ASYNC, new DispatcherType[]{DispatcherType.ERROR, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST});
        filterRegistrationBean.setOrder(-2147483643);
        return filterRegistrationBean;
    }

    @Bean
    @ConditionalOnMissingBean
    public TracingFilter tracingFilter(HttpTracing tracing) {
        return (TracingFilter)TracingFilter.create(tracing);
    }
拦截器注册

然后看TraceWebMvcConfigurer类,它会进行拦截器的注册。

@Configuration
@Import({SpanCustomizingAsyncHandlerInterceptor.class})
class TraceWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    ApplicationContext applicationContext;

    TraceWebMvcConfigurer() {
    }

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor((HandlerInterceptor)this.applicationContext.getBean(SpanCustomizingAsyncHandlerInterceptor.class));
    }
}

SpanCustomizingAsyncHandlerInterceptor类中,preHandle,afterCompletion方法使用Brave对请求进行拦截进行span的包装。Brave是Java版的Zipkin客户端,它将收集的跟踪信息,以Span的形式上报给Zipkin系统。

public final class SpanCustomizingAsyncHandlerInterceptor extends HandlerInterceptorAdapter {
    @Autowired(
        required = false
    )
    HandlerParser handlerParser = new HandlerParser();

    SpanCustomizingAsyncHandlerInterceptor() {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) {
        SpanCustomizer span = (SpanCustomizer)request.getAttribute(SpanCustomizer.class.getName());
        if (span != null) {
            this.handlerParser.preHandle(request, o, span);
        }

        return true;
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        SpanCustomizer span = (SpanCustomizer)request.getAttribute(SpanCustomizer.class.getName());
        if (span != null) {
            SpanCustomizingHandlerInterceptor.setHttpRouteAttribute(request);
        }

    }
}
zipkin端点提交

ZipkinAutoConfiguration会进行端点的提交。

public class ZipkinAutoConfiguration {
    public ZipkinAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public Reporter<Span> reporter(ReporterMetrics reporterMetrics, ZipkinProperties zipkin, Sender sender, BytesEncoder<Span> spanBytesEncoder) {
        return AsyncReporter.builder(sender).queuedMaxSpans(1000).messageTimeout((long)zipkin.getMessageTimeout(), TimeUnit.SECONDS).metrics(reporterMetrics).build(spanBytesEncoder);
    }

    @Bean
    @ConditionalOnMissingBean
    public BytesEncoder<Span> spanBytesEncoder(ZipkinProperties zipkinProperties) {
        return zipkinProperties.getEncoder();
    }

    @Bean
    @ConditionalOnMissingBean
    public ZipkinRestTemplateCustomizer zipkinRestTemplateCustomizer(ZipkinProperties zipkinProperties) {
        return new DefaultZipkinRestTemplateCustomizer(zipkinProperties);
    }

    @Bean
    @ConditionalOnMissingBean
    ReporterMetrics sleuthReporterMetrics() {
        return new InMemoryReporterMetrics();
    }

    @Configuration
    @ConditionalOnClass({Registration.class})
    @ConditionalOnMissingBean({EndpointLocator.class})
    @ConditionalOnProperty(
        value = {"spring.zipkin.locator.discovery.enabled"},
        havingValue = "true"
    )
    protected static class RegistrationEndpointLocatorConfiguration {
        @Autowired(
            required = false
        )
        private ServerProperties serverProperties;
        @Autowired
        private ZipkinProperties zipkinProperties;
        @Autowired(
            required = false
        )
        private InetUtils inetUtils;
        @Autowired
        private Environment environment;
        @Autowired(
            required = false
        )
        private Registration registration;

        protected RegistrationEndpointLocatorConfiguration() {
        }

        @Bean
        public EndpointLocator zipkinEndpointLocator() {
            return new DefaultEndpointLocator(this.registration, this.serverProperties, this.environment, this.zipkinProperties, this.inetUtils);
        }
    }

    @Configuration
    @ConditionalOnMissingBean({EndpointLocator.class})
    @ConditionalOnProperty(
        value = {"spring.zipkin.locator.discovery.enabled"},
        havingValue = "false",
        matchIfMissing = true
    )
    protected static class DefaultEndpointLocatorConfiguration {
        @Autowired(
            required = false
        )
        private ServerProperties serverProperties;
        @Autowired
        private ZipkinProperties zipkinProperties;
        @Autowired(
            required = false
        )
        private InetUtils inetUtils;
        @Autowired
        private Environment environment;

        protected DefaultEndpointLocatorConfiguration() {
        }

        @Bean
        public EndpointLocator zipkinEndpointLocator() {
            return new DefaultEndpointLocator((Registration)null, this.serverProperties, this.environment, this.zipkinProperties, this.inetUtils);
        }
    }

    @Configuration
    @ConditionalOnMissingClass({"org.springframework.cloud.context.config.annotation.RefreshScope"})
    protected static class NonRefreshScopeProbabilityBasedSamplerConfiguration {
        protected NonRefreshScopeProbabilityBasedSamplerConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        public Sampler defaultTraceSampler(SamplerProperties config) {
            return new ProbabilityBasedSampler(config);
        }
    }

    @Configuration
    @ConditionalOnClass({RefreshScope.class})
    protected static class RefreshScopedProbabilityBasedSamplerConfiguration {
        protected RefreshScopedProbabilityBasedSamplerConfiguration() {
        }

        @Bean
        @RefreshScope
        @ConditionalOnMissingBean
        public Sampler defaultTraceSampler(SamplerProperties config) {
            return new ProbabilityBasedSampler(config);
        }
    }
}

reporter: 内部会初始化sender以及delegate。sender是以何种方式将zipkin span发送到zipkin server。delegate是一个委托类,内部创建一个有界队列,异步将zipkin span发送到zipkin server。

zipkinRestTemplateCustomizer: 创建reporter bean时需要依赖的sender。这里zipkin使用restTemplate作为sender提交span。

调用http接口时,进入过滤器

进入TraceWebFilter中的过滤方法filter

    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        if (this.tracer().currentSpan() != null) {
            this.tracer().withSpanInScope((Span)null);
        }

        String uri = exchange.getRequest().getPath().pathWithinApplication().value();
        if (log.isDebugEnabled()) {
            log.debug("Received a request to uri [" + uri + "]");
        }

        Span spanFromAttribute = this.getSpanFromAttribute(exchange);
        String CONTEXT_ERROR = "sleuth.webfilter.context.error";
        return chain.filter(exchange).compose((f) -> {
            return f.then(Mono.subscriberContext()).onErrorResume((t) -> {
                return Mono.subscriberContext().map((c) -> {
                    return c.put("sleuth.webfilter.context.error", t);
                });
            }).flatMap((c) -> {
                Span span = this.spanFromContext(c);
                Throwable t = null;
                Mono continuation;
                if (c.hasKey("sleuth.webfilter.context.error")) {
                    t = (Throwable)c.get("sleuth.webfilter.context.error");
                    continuation = Mono.error(t);
                } else {
                    continuation = Mono.empty();
                }

                String httpRoute = null;
                Object attribute = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
                if (attribute instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod)attribute;
                    this.addClassMethodTag(handlerMethod, span);
                    this.addClassNameTag(handlerMethod, span);
                    Object pattern = exchange.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
                    httpRoute = pattern != null ? pattern.toString() : "";
                }

                this.addResponseTagsForSpanWithoutParent(exchange, exchange.getResponse(), span);
                TraceWebFilter.DecoratedServerHttpResponse delegate = new TraceWebFilter.DecoratedServerHttpResponse(exchange.getResponse(), exchange.getRequest().getMethodValue(), httpRoute);
                this.handler().handleSend(delegate, t, span);
                if (log.isDebugEnabled()) {
                    log.debug("Handled send of " + span);
                }

                return continuation;
            }).subscriberContext((c) -> {
                Span span;
                if (c.hasKey(Span.class)) {
                    Span parent = (Span)c.get(Span.class);
                    span = this.tracer().nextSpan(TraceContextOrSamplingFlags.create(parent.context())).start();
                    if (log.isDebugEnabled()) {
                        log.debug("Found span in reactor context" + span);
                    }
                } else {
                    if (spanFromAttribute != null) {
                        span = spanFromAttribute;
                        if (log.isDebugEnabled()) {
                            log.debug("Found span in attribute " + spanFromAttribute);
                        }
                    } else {
                        span = this.handler().handleReceive(this.extractor(), exchange.getRequest().getHeaders(), exchange.getRequest());
                        if (log.isDebugEnabled()) {
                            log.debug("Handled receive of span " + span);
                        }
                    }

                    exchange.getAttributes().put(TRACE_REQUEST_ATTR, span);
                }

                return c.put(Span.class, span);
            });
        });
    }
发布了44 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hxyascx/article/details/103969286