Spring Cloud Learn - Link Trace: sleuth

Why Spring Cloud Sleuth

Micro-service architecture is a distributed architecture, which by business services unit, a distributed system tends to have a number of service units. Due to the large number of service units, the complexity of the business, if something goes wrong and abnormal, it is difficult to locate. Mainly reflected in, a request may need to call a number of services, and call complexity of internal services, it determines the problem is difficult to locate. Therefore, the micro-service architecture, must implement a distributed link tracking, to follow up a request in the end what services involved in order to participate and how to achieve the steps each request is clearly visible, out of the question, quickly positioning .

Spring Cloud Sleuth main function is to provide tracking solutions in a distributed system, you only need to introduce appropriate to rely on the pom file.

the term

spring cloud sleuth follow Google's Dapper terms:

  • trace: a tree structure composed of numerous span, generates a unique 64-bit identifier scheduling ID.
  • span: the basic unit of work, for example, in a transmission span in a new RPC is equivalent to sending a response to the RPC requests, span, another 64-bit ID to the trace represented by a 64-bit unique identification ID, there are other data span such as abstract, timestamp events, key values comment (tags), span the ID, and schedule ID (usually an IP address)
    span in constant starts and stops, while recording the time information, when you create a span, you it must stop at some point in the future.
  • Annotation: used to record the presence of a timely event, some of the core annotations to define the start and end of a request:
    • cs: Client Sent - the client initiates a request, this annotation describes the start of the span.
    • sr: Server Received - server request and get ready to deal with it, if it sr cs timestamp can be obtained by subtracting the network latency.
    • ss: Server Sent - notes that the request to complete the process (when the request back to the client), if sr ss minus the timestamp can be processed server needs to request time.
    • cr: Client Received - indicates the end, the client receives the reply span of successful service side, if cr cs timestamp can be obtained by subtracting the time required for all clients get a reply from the server.
      Here Insert Picture Description

Source resolve

spring-cloud-sleuth-core structure:
Here Insert Picture Description

  • You can see the source code to support many types of tracking, supporting async, hystrix, websocket, rxjava, Spring mvc, servlet, spring restTemplate, feign, zuul and so on, where I focused on the spring web mvc link tracking.
  • Open a web package, find TraceWebServletAutoConfiguration, here configured, the primary initialization class.

Here Insert Picture Description

Filter Registration

When the initialization routine, the tracking TraceWebServletAutoConfigurationcode is as follows

    @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);
    }
Interceptor registration

Then look at TraceWebMvcConfigurerthe class, it will be registered interceptors.

@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));
    }
}

In SpanCustomizingAsyncHandlerInterceptorclass preHandle, afterCompletionmethod Brave request intercept span for packaging. Brave is a Java version of the Zipkin client tracking information it collected, reported in the form of Span to Zipkin system.

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 endpoint submitted

ZipkinAutoConfigurationIt will be submitted to the endpoint.

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: Internal initialized sender and delegate. the manner in which the sender sent to zipkin server zipkin span. delegate is a delegate class, created inside a bounded queue, asynchronously sent to the zipkin server zipkin span.

zipkinRestTemplateCustomizer: need to rely on the sender when creating reporter bean. Here zipkin use restTemplate submit span as the sender.

When call http interface into the filter

Entering TraceWebFilterthe filter methodfilter

    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);
            });
        });
    }
Published 44 original articles · won praise 9 · views 10000 +

Guess you like

Origin blog.csdn.net/hxyascx/article/details/103969286