关于springcloud:SpringCloud源码解析-Spring-Cloud-Sleuth原理探究

14次阅读

共计 5002 个字符,预计需要花费 13 分钟才能阅读完成。

本文分享 spring cloud sleuth 如何构建申请调用链路,并上报 zipkin。
如果大家在应用 spring cloud sleuth 时遇到 traceId 失落,上报 zipkin 失败等问题,心愿本文能够给大家一个思路。
源码剖析基于 Spring Cloud Hoxton,Spring Cloud Sleuth 版本为 2.2.0.RELEASE

首先明确几个概念
Span:一个工作单元,比方一次 RPC(Http) 申请过程,其 SpanId 变量应用惟一的 64 位 ID 标识一个 Span,Span 还有其余数据,如形容,工夫戳,Annotation(tags)。
Trace:一条申请调用链路组成的 span 汇合,相似于 tree 构造,同样,TraceId 变量标识一个 Trace。

Annotation:用于记录事件理论产生工夫,可用于 zipkin 统计和绘制报表。
cs:Client Sent,客户端发动一个申请工夫
sr:Server Received,服务端接管到申请工夫
ss:Server Sent,服务端返回响应工夫
cr:Client Received,客户端接管到响应工夫

SpringBoot 2 开始应用了 brave 框架实现日志收集,brave 是 zipkin 官网提供的 java 版本客户端,它将收集的跟踪信息,以 Span 的模式上报给 Zipkin 零碎。
brave 框架具体可见 https://github.com/openzipkin…。

上面假如有 client,server 两个工程,调用链路如下:
用户 -> client --> server

本文咱们来探讨以下几个问题

  1. 用户调用 client 时,client 如何生成 Span 信息
  2. client 调用 server 时,如何将 Span 发送到 server
  3. server 如何接管 cliend 的 Span 信息
  4. client,server 如何发送 Span 到 zipkin

留神,本文是基于 SpringMvc+RestTeamplate 的申请调用的,而非 WebFlux 异步调用。

问题 1

问题 1 和问题 3,都由 LazyTracingFilter 解决,它在 TraceWebServletAutoConfiguration 中初始化。

(本文局部源码来自于 brave 框架)
LazyTracingFilter#doFilter -> (brave)TracingFilter#doFilter

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
...

    // #1
    Span span = handler.handleReceive(new HttpServerRequest(httpRequest));

    // #2
    request.setAttribute(SpanCustomizer.class.getName(), span.customizer());
    request.setAttribute(TraceContext.class.getName(), span.context());

    Throwable error = null;
    
    // #3
    Scope scope = currentTraceContext.newScope(span.context());
    try {chain.doFilter(httpRequest, httpResponse);
    } ...
    finally {
      // #4
      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(servlet.httpServerResponse(httpRequest, httpResponse), error, span);
      }
    }
}

#1
从 Http Request 的 Header 里获取 Span 数据,
如果 Header 中存在 X -B3-TraceId,X-B3-SpanId,X-B3-ParentSpanId 属性,就阐明调用链前一个节点曾经生成 Span,并传递下来,这时能够间接应用这些 Span 数据。
否则,创立一个新的 Span。
#2 记录一些 Span 的属性
#3 调用 ThreadLocalCurrentTraceContext#newScope,保留以后的 Span 信息,当利用向下传递 Span 信息时须要应用这些信息。
这里会调用 ThreadLocalCurrentTraceContext#decorateScope,它会获取上下文的 ScopeDecorator,并触发其 decorateScope 办法。
SleuthLogAutoConfiguration 构建了默认的 ScopeDecorator — Slf4jScopeDecorator,它会获取 Span 中的 traceId,parentId,spanId,搁置 MDC 中,不便日志打印。
TraceAutoConfiguration 负责构建 ThreadLocalCurrentTraceContext,并将 Slf4jScopeDecorator 增加到 ThreadLocalCurrentTraceContext#decorateScope 中
#4 敞开 Scope,记录 server finishTimestamp(ss)。

HttpServerHandler#handleReceive

public Span handleReceive(HttpServerRequest request) {
    // #1
    Span span = nextSpan(defaultExtractor.extract(request), request);
    // #2
    return handleStart(new HttpServerRequest.ToHttpAdapter(request), request.unwrap(), span);
}

#1 调用 B3Extractor#extract,会从 Http Header 中提取 X -B3-TraceId,X-B3-SpanId,X-B3-ParentSpanId 属性。
nextSpan 办法依据提取的后果,调用 tracer.joinSpan 或 tracer.nextSpan 办法。
#2 记录 http.method,http.path,mvc.controller.class 等 Annotation,并记录 server startTimestamp(sr)。

问题 2

LazyTracingClientHttpRequestInterceptor 负责实现该性能。它在 TraceWebClientAutoConfiguration 中构建。

LazyTracingClientHttpRequestInterceptor#interceptor -> TracingClientHttpRequestInterceptor#intercept(brave)

public ClientHttpResponse intercept(HttpRequest request, byte[] body,
    ClientHttpRequestExecution execution) throws IOException {
    // #1
    Span span = handler.handleSend(new HttpClientRequest(request));
    HttpClientResponse response = null;
    Throwable error = null;
    // #2
    try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
      // #3
      ClientHttpResponse result = execution.execute(request, body);
      response = new HttpClientResponse(result);
      return result;
    } catch (IOException | RuntimeException | Error e) {
      error = e;
      throw e;
    } finally {
      // #4
      handler.handleReceive(response, error, span);
    }
}

#1 将 Span 信息注入 Http 申请中
#2 将 Span 包装成 Tracer.SpanInScope
#3 发送申请
#4 记录 client finishTimestamp(cr),并且上报 span

HttpClientHandler#handleSend

public Span handleSend(HttpClientRequest request, Span span) {
    ...
    // #1
    defaultInjector.inject(span.context(), request);
    // #2
    return handleStart(new HttpClientRequest.ToHttpAdapter(request), request.unwrap(), span);
}

#1
span 是通过 Tracer#nextSpan 获取,该办法从 ThreadLocalCurrentTraceContext#get 获取以后的 Span 信息。
再调用 B3Propagation#inject 办法将 traceId,parentId,spanId 注入 Http Header 中。
#2 记录 Span 相干信息,如 client startTimestamp(cs)。

问题 4

那么 Spring 如何将 span 发送到 zipkin?

先看一个 brave 框架上报 Span 的简略例子

Sender sender = URLConnectionSender.create("http://localhost:9411/api/v1/spans")
AsyncReporter asyncReporter = AsyncReporter.builder(sender)
        .build(SpanBytesEncoder.JSON_V2);

Tracing tracing = Tracing.newBuilder()
        .spanReporter(asyncReporter)
        .build();

Tracer tracer = tracing.tracer();
Span span = tracer.newTrace().name("encode").start();
...
span.finish();

咱们并不要本人上报数据,span.finish()办法中,brave 会帮咱们实现上报 Span 工作。

看一下上例中 brave 的组件类
Tracing:负责提供其余组件类,如 Tracer,Sampler。在 TraceAutoConfiguration 中构建。
Tracer:治理一个申请链路,能够创立 Span。
Sender:负责将编码后的二进制数据发送给 Zipkin。在 ZipkinRestTemplateSenderConfiguration 中构建一个 RestTemplateSender,应用 RestTemplate 上报 Span 数据。
AsyncReporter:异步上报器,调度 Sender 实现 Span 上报工作。在 ZipkinAutoConfiguration 中构建。

在 Spring Cloud Sleuth 中,
TracingClientHttpRequestInterceptor#intercept 办法 ’#4’ 步骤 -> RealSpan#finish -> ZipkinFinishedSpanHandler#handle。
ZipkinFinishedSpanHandler#handle 会调用 Reporter#report 来上报 Span 数据
ZipkinFinishedSpanHandler 是 Tracing 默认的 FinishedSpanHandler。

如果您感觉本文不错,欢送关注我的微信公众号,您的关注是我保持的能源!

正文完
 0