ServiceComb + Zipkin : 源码解读

ServiceComb + Zipkin : 源码解读

SeviceComb + Zipkin 简介

ServiceComb 是Apache开源的微服务架构,在微服务框架中,服务通过网络进行通信,我们必须处理所有与网络相关的问题,例如延迟,超时和分区。随着部署的服务越来越多,我们需要系统监控微服务网络延迟和请求流。

Zipkin是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计基于Google Dapper论文。参见Zipkin官网1

ServiceComb与Zipkin集成以提供自动跟踪,以便用户可以专注于满足他们的业务需求。

ServiceComb 如何支持zipkin

ServiceComb 提供了处理链机制,通过扩展handler处理链接口,已经实现负载均衡、熔断容错、流量控制等能力。同样,实现调用链追踪能力,也可以通过扩展处理链接口实现调用链追踪处理链。了解ServiceComb处理链参考 https://docs.servicecomb.io/java-chassis/zh_CN/references-handlers/intruduction.html

ServiceComb 扩展handler处理链接口,编写了handler-tracing-zipkin 模块,用于支持追踪基于ServiceComb java chassis 的微服务。Handler-tracing-zipkin 模块在java-chassis/handler处理链下,模块内容如下。那么handler-tracing-zipkin 模块究竟做了什么去支持zipkin调用链追踪?

ServiceComb + Zipkin : 源码解读_第1张图片
事实上,zipkin服务端提供了数据收集、存储、API查询检索、Web UI等服务,我们只需要向zipkin服务上传调用链追踪数据,即可在zipkin提供的Web页面查看服务调用链追踪信息。如此一来,我们只需要使用zipkin的brave组件跟踪服务调用链并记录追踪数据,再使用reporter组件上传追踪数据到zipkin服务即可。

按照这个思路,ServiceComb只需要在后台启动zipkin服务,使用上面的Handler-tracing-zipkin模块实现追踪调用链记录数据和上传追踪数据到zipkin服务,即可支持zipkin调用链追踪。

ServiceComb项目如何使用zipkin实施调用链追踪,参考Distributed Tracing with ServiceComb and Zipkin2

ServiceComb 的 handler-tracing-zipkin模块源码解读

上面提到我们只需要追踪调用链、记录数据、上传追踪数据到zipkin服务即可支持zipkin调用链追踪。下面我们从编码角度,去介绍如何具体实现它。
追踪调用链首先需要在程序中初始化配置zipkin追踪组件(zipkin提供了一系列用于调用链柱子的组件),然后适配追踪组件到调用链请求上(zipkin在调用链请求和追踪组件之间提供了一系列适配接口和类)。调用链追踪组件设置完毕,我们只需要定义好一系列追踪方法和调用这些方法即可。上面的过程可以归结为以下4步:

  1. 初始化配置追踪组件
  2. 适配分布式调用链
  3. 追踪分布式调用链,记录并发送追踪数据到zipkin
  4. 调用上述追踪方法,追踪调用链,并记录日志

按照上面4步,我们实现了handler-tracing-zipkin 模块。但由于消费端和服务端对于调用链请求的适配追踪处理方法不同,handler-tracing-zipkin 模块分别实现了消费端和服务端追踪。下面我们从handler-tracing-zipkin 模块UML类图,去解读模块内类关系结构。
ServiceComb + Zipkin : 源码解读_第2张图片
从handler-tracing-zipkin模块UML类图可以看到:

  • TracingConfiguration类:初始化配置追踪组件,供调用链追踪使用。
  • ConsumerInvocationAdapter 和ProviderInvocationAdapter 调用链适配类:分别继承了zipkin提供的消费端和服务端调用链适配器接口,分别支持消费端和服务端适配调用链。
  • ZipkinConsumerDelegate 和 ZipkinProviderDelegate 类:实现了ZipkinTracingDelegate接口,分别依赖ConsumerInvocationAdapter 和ProviderInvocationAdapter 调用链适配类,定义了消费端和服务端调用链追踪方法。
  • ZipkinTracingHandler 类:实现了handler处理链接口,调用成员变量ZipkinTracingDelegate 定义的方法,追踪调用链,并记录日志。
  • ZipkinConsumerTracingHandler 类 和 ZipkinProviderInvocationAdapter 类继承了ZipkinTracingHandler调用链处理类,为消费端调用链追踪入口和服务端调用链追踪入口,如下,在cse.handler.xml中分别对应handler-id为"tracing-consumer"和"tracing-provider",handler-id为handler处理链名字,帮助应用配置调用链追踪处理链,如何配置可参考(http://servicecomb.apache.org/docs/tracing-with-servicecomb/)2
    <config>
      <handler id="tracing-consumer"
        class="org.apache.servicecomb.tracing.zipkin.ZipkinConsumerTracingHandler"/>
      <handler id="tracing-provider"
        class="org.apache.servicecomb.tracing.zipkin.ZipkinProviderTracingHandler"/>
    </config>
    

下面按照分布式调用链追踪步骤,深入代码解读handler-tracing-zipkin模块内类

  1. 初始化配置追踪组件:TracingConfiguration 类

    class TracingConfiguration {
      private String apiVersion = CONFIG_TRACING_COLLECTOR_API_V2;
      //定义sender方法	
      @Bean
      Sender sender(DynamicProperties dynamicProperties) {
        apiVersion = dynamicProperties.getStringProperty(CONFIG_TRACING_COLLECTOR_API_VERSION,
            CONFIG_TRACING_COLLECTOR_API_V2).toLowerCase();
        // use default value if the user set value is invalid
        if (apiVersion.compareTo(CONFIG_TRACING_COLLECTOR_API_V1) != 0) {
          apiVersion = CONFIG_TRACING_COLLECTOR_API_V2;
        }
    
        String path = MessageFormat.format(CONFIG_TRACING_COLLECTOR_PATH, apiVersion);
        return OkHttpSender.create(
            dynamicProperties.getStringProperty(
                CONFIG_TRACING_COLLECTOR_ADDRESS,
                DEFAULT_TRACING_COLLECTOR_ADDRESS)
                .trim()
                .replaceAll("/+$", "")
                .concat(path));
      }
      //初始化reporter组件
      @Bean
      Reporter<Span> zipkinReporter(Sender sender) {
        if (apiVersion.compareTo(CONFIG_TRACING_COLLECTOR_API_V1) == 0) {
          return AsyncReporter.builder(sender).build(SpanBytesEncoder.JSON_V1);
        }
    
        return AsyncReporter.builder(sender).build();
      }
      //初始化tracing追踪组件
      @Bean
      Tracing tracing(Reporter<Span> reporter, DynamicProperties dynamicProperties,
          CurrentTraceContext currentTraceContext) {
        return Tracing.newBuilder()
            .localServiceName(dynamicProperties.getStringProperty(CONFIG_QUALIFIED_MICROSERVICE_NAME_KEY,
                DEFAULT_MICROSERVICE_NAME))
            .currentTraceContext(currentTraceContext) // puts trace IDs into logs
            .spanReporter(reporter)
            .build();
      }
      //定义currentTraceContext方法,获取追踪信息
      @Bean
      CurrentTraceContext currentTraceContext() {
        return ThreadLocalCurrentTraceContext.newBuilder().addScopeDecorator(MDCScopeDecorator.create()).build();
      }
      //配置httpTracing
      @Bean
      HttpTracing httpTracing(Tracing tracing) {
        return HttpTracing.create(tracing);
      }
    }
    

    TracingConfiguration初始化配置类,首先初始化了Tracing,然后配置了httpTracing,使用@bean生成httpTracing方法的bean对象加载到spring管理的容器中。httpTracing包含对http追踪组件的引用,可用于创建http追踪处理器。初始化配置httpTracing主要包括配置追踪服务名字、定义 currentTraceContext 方法(用于获取追踪信息)、配置reporter方法以及sender发送组件(用于发送追踪数据到zipkin)。

  2. ConsumerInvocationAdapter 类和 ProviderInvocationAdapter 类
    消费端追踪适配调用链:ConsumerInvocationAdapter 类

    //ConsumerInvocationAdapter继承了brave.http.HttpClientAdapter客户端调用链适配类
    class ConsumerInvocationAdapter extends HttpClientAdapter<Invocation, Response> {
      // 获取调用链请求的方法
      @Nullable
      @Override
      public String method(@Nonnull Invocation invocation) {
        return invocation.getOperationMeta().getHttpMethod();
      }
      // 获取调用链请求的url
      @Nullable
      @Override
      public String url(@Nonnull Invocation invocation) {
        return invocation.getEndpoint().getEndpoint();
      }
      // 获取调用链请求的path	
      @Nullable
      @Override
      public String path(@Nonnull Invocation request) {
        return request.getOperationMeta().getOperationPath();
      }
      // 获取调用链请求头
      @Nullable
      @Override
      public String requestHeader(@Nonnull Invocation invocation, @Nonnull String key) {
        return invocation.getContext().get(key);
      }
      //获取调用链请求响应状态
      @Nullable
      @Override
      public Integer statusCode(@Nonnull Response response) {
        return response.getStatusCode();
      }
    }
    

    ConsumerInbvocationAdapter类,继承了zipkin-brave组件的HttpClientAdapter客户端适配类,主要用于追踪组件适配消费端调用链,定义了一系列方法,用于获取调用链请求的method、url、path、requestHeader、statusCode。获取的信息有助于记录消费端追踪数据Span,比如说Span的name一般设置为请求的method(GET或POST),Span会记录请求的 url 和 path。追踪消费端调用链请求需要绑定请求的requestHeader,结束追踪需要根据请求的statusCode。

    服务端追踪适配调用链:ProviderInvocationAdapter 类

    // ProviderInvocationAdapter 继承了brave.http.HttpServerAdapter服务端调用链适配类
    class ProviderInvocationAdapter extends HttpServerAdapter<Invocation, Response> {
    
      @Nullable
      @Override
      public String method(@Nonnull Invocation invocation) {
        return invocation.getOperationMeta().getHttpMethod();
      }
    
      @Nullable
      @Override
      public String url(@Nonnull Invocation invocation) {
        return invocation.getEndpoint().getEndpoint();
      }
    
      @Nullable
      @Override
      public String path(@Nonnull Invocation request) {
        return request.getOperationMeta().getOperationPath();
      }
    
      @Nullable
      @Override
      public String requestHeader(@Nonnull Invocation invocation, @Nonnull String key) {
        return invocation.getContext().get(key);
      }
    
      @Nullable
      @Override
      public Integer statusCode(@Nonnull Response response) {
        return response.getStatusCode();
      }
    }
    

    ProviderInvocationAdapter类,继承了zipkin-brave组件的HttpProviderAdapter服务端适配类,主要用于追踪组件适配服务端调用链,定义了一系列方法去获取http请求的method、url、path,、requestHeader、statusCode。获取信息有助于记录服务端追踪数据Span,比如说Span的name一般设置为请求的method(GET或POST),Span会记录请求的 url 和 path。追踪服务端调用链请求需要获取请求的requestHeader内容,结束追踪需要根据请求的statusCode。

  3. ZipkinConsumerDelegate 类和 ZipkinProviderDelegate 类
    消费端追踪方法定义:ZipkinConsumerDelegate 类

    class ZipkinConsumerDelegate implements ZipkinTracingDelegate {
    
      private final HttpClientHandler<Invocation, Response> handler;
    
      private final HttpTracing httpTracing;
    
      private final Injector<Invocation> injector;
    
      @SuppressWarnings("unchecked")
      ZipkinConsumerDelegate(HttpTracing httpTracing) {
        this.httpTracing = httpTracing;
        this.injector = httpTracing.tracing().propagation().injector(injector());
        this.handler = HttpClientHandler.create(httpTracing, new ConsumerInvocationAdapter());
      }
      // 追踪消费端发送的调用链请求,创建Span
      @Override
      public Span createSpan(Invocation invocation) {
        return handler.handleSend(injector, invocation);
      }
      // 收到响应,上报Span到zipkin
      @Override
      public void onResponse(Span span, Response response, Throwable error) {
        handler.handleReceive(response, error, span);
      }
      // 定义消费端追踪名字为Zipkin consumer
      @Override
      public String name() {
        return "Zipkin consumer";
      }
      //  获取httpTracing的Tracing组件
      @Override
      public Tracing tracer() {
        return httpTracing.tracing();
      }
      // 在调用链请求中设置值
      private Setter<Invocation, String> injector() {
        return (invocation, key, value) -> invocation.getContext().put(key, value);
      }
    }
    

    ZipkinConsumerDelegate类,从消费端的调用链追踪实现了ZipkinTracingDelegate接口定义的追踪方法。首先创建了追踪处理器handler和注射器injector。然后定义了createSpan和onResponse方法,使用处理器向消费端发送的调用链请求中注入追踪ID、创建Span记录追踪数据,收到响应,发送追踪数据到zipkin。
    this.handler = HttpClientHandler.create(httpTracing, new ConsumerInvocationAdapter()): 依赖httpTracing追踪组件 和 ConsumerInvocationAdapter 适配器,创建了handler追踪处理器。
    this.injector = httpTracing.tracing().propagation().injector(injector()): 初始化定义了injector,用于将追踪器信息到设置在调用链请求中。
    createSpan(Invocation):调用handler处理器的handlerSend方法,使用injector绑定追踪器到http请求,创建Span,开始记录追踪数据。
    onResponse(Span,Response,Throwable):调用handler处理器的handlerReceive方法,接收到服务端响应后,完成Span,触发reporter组件上报Span数据到zipkin。

    服务端追踪方法定义:ZipkinProviderDelegate 类

    class ZipkinProviderDelegate implements ZipkinTracingDelegate {
      private static final Logger LOG = LoggerFactory.getLogger(ZipkinProviderDelegate.class);
    
      private final HttpServerHandler<Invocation, Response> handler;
    
      private final HttpTracing httpTracing;
    
      private final Extractor<Invocation> extractor;
    
      public static final String SPAN_ID_HEADER_NAME = "X-B3-SpanId";
    
      public static final String TRACE_ID_HEADER_NAME = Const.TRACE_ID_NAME;
      //在调用链请求中获取值 
      private static final Getter<Invocation, String> INVOCATION_STRING_GETTER = (invocation, key) -> {
        String extracted = invocation.getContext().get(key);
        if (StringUtils.isEmpty(extracted) && SPAN_ID_HEADER_NAME.equals(key)) {
          // use traceId as spanId to avoid brave's recreating traceId
          extracted = invocation.getContext().get(TRACE_ID_HEADER_NAME);
          LOG.debug("try to extract X-B3-SpanId, but the value is empty, replace with TraceId = [{}]", extracted);
        }
        return extracted;
      };
      
      @SuppressWarnings("unchecked")
      ZipkinProviderDelegate(HttpTracing httpTracing) {
        this.httpTracing = httpTracing;
        this.extractor = httpTracing.tracing().propagation().extractor(extractor());
        this.handler = HttpServerHandler.create(httpTracing, new ProviderInvocationAdapter());
      }
      // 获取httpTracing的Tracing组件
      @Override
      public Tracing tracer() {
        return httpTracing.tracing();
      }
      //追踪收到的调用链请求,创建Span
      @Override
      public Span createSpan(Invocation invocation) {
        return handler.handleReceive(extractor, invocation);
      }
      // 发出响应,上报Span到zipkin服务
      @Override
      public void onResponse(Span span, Response response, Throwable error) {
        handler.handleSend(response, error, span);
      }
      // 定义服务端追踪名字为Zipkin provider
      @Override
      public String name() {
        return "Zipkin provider";
      }
    
      private Getter<Invocation, String> extractor() {
        return INVOCATION_STRING_GETTER;
      }
    }
    

    ZipkinProviderDelegate类,从服务端的调用链追踪实现了ZipkinTracingDelegate接口定义的追踪方法。首先创建了追踪处理器handler和获取器extractor,然后定义了createSpan和onResponse方法,使用处理器从服务端接收的调用链请求中获取追踪ID、创建Span记录追踪数据、处理完成发送响应,发送追踪数据到zipkin。
    this.handler = HttpServerHandler.create(httpTracing, new ProviderInvocationAdapter()): 依赖httpTracing追踪组件 和 ProviderInvocationAdapter适配器 创建了handler追踪处理器。
    this.extractor = httpTracing.tracing().propagation().extractor(extractor()): 初始化定义了extractor,用于从调用链请求中获取追踪信息。
    createSpan(Invocation):调用handler处理器的handleReceive方法,使用extractor从服务端收到的http请求中获取追踪内容,创建Span,开始记录追踪数据。
    onResponse(Span,Response,Throwable):调用handler处理器的handleSend方法,处理完成发送响应后,完成Span,触发reporter组件上报Span数据到zipkin。

  4. 追踪处理:ZipkinTracingHandler 类

    // ZipkinTracingHandler 实现了 org.apache.servicecomb.core.Handler.handler 处理链接口
    class ZipkinTracingHandler implements Handler {
    
      private static final Logger LOGGER = LoggerFactory.getLogger(ZipkinTracingHandler.class);
    
      private final Tracing tracer;
    
      private final ZipkinTracingDelegate tracingDelegate;
    
      ZipkinTracingHandler(ZipkinTracingDelegate tracingDelegate) {
        this.tracer = tracingDelegate.tracer();
        this.tracingDelegate = tracingDelegate;
      }
      //实现zipkin调用链追踪的处理函数
      @SuppressWarnings({"try", "unused"})
      @Override
      public void handle(Invocation invocation, AsyncResponse asyncResp) throws Exception {
        Span span = tracingDelegate.createSpan(invocation);
        try (SpanInScope scope = tracer.tracer().withSpanInScope(span)) {
          LOGGER.debug("{}: Generated tracing span for {}",
              tracingDelegate.name(),
              invocation.getOperationName());
    	  
          invocation.next(onResponse(invocation, asyncResp, span));
        } catch (Exception e) {
          LOGGER.debug("{}: Failed invocation on {}",
              tracingDelegate.name(),
              invocation.getOperationName(),
              e);
    
          tracingDelegate.onResponse(span, null, e);
          throw e;
        }
      }
      //调用链请求响应,上报Span数据
      private AsyncResponse onResponse(Invocation invocation, AsyncResponse asyncResp, Span span) {
        return response -> {
          Throwable error = response.isFailed() ? response.getResult() : null;
          tracingDelegate.onResponse(span, response, error);
    
          LOGGER.debug("{}: Completed invocation on {}",
              tracingDelegate.name(),
              invocation.getOperationName(),
              error);
    
          asyncResp.handle(response);
        };
      }
    }
    

    ZipkinTracingHandler类实现了ServiceComb的handler处理链接口,调用追踪方法处理调用链请求。handler处理链是ServiceComb的核心部分,具体可参见java-chassiss使用手册——调用参考3。ZipkinTracingHandler 调用了ZipkinConsumerDelegate 的createSpan方法追踪消费端调用链,创建Span, 记录追踪数据;调用了onResponse方法接收到响应后,完成追踪调用链,并把调用链追踪情况反馈到日志中。

  5. ZipkinConsumerTracingHandler 类 和 ZipkinProviderTracingHandler 类
    消费端追踪处理:ZipkinConsumerTracingHandler 类

    public class ZipkinConsumerTracingHandler extends ZipkinTracingHandler {
    
      public ZipkinConsumerTracingHandler() {
        this(new ZipkinConsumerDelegate(BeanUtils.getContext().getBean(HttpTracing.class)));
      }
    
      private ZipkinConsumerTracingHandler(ZipkinTracingDelegate tracingHandler) {
        super(tracingHandler);
      }
    }
    

    服务端追踪处理:ZipkinProviderTracingHandler 类

    public class ZipkinProviderTracingHandler extends ZipkinTracingHandler {
    
      public ZipkinProviderTracingHandler() {
        this(new ZipkinProviderDelegate(BeanUtils.getContext().getBean(HttpTracing.class)));
      }
    
      private ZipkinProviderTracingHandler(ZipkinProviderDelegate tracingHandler) {
        super(tracingHandler);
      }
    }
    

    ZipkinConsumerTracingHandler 类 和 ZipkinProviderTracingHandler 类 继承了ZipkinTracingHandler 追踪处理类。从spring的容器中获取httpTracing的bean对象(TracingConfiguration 中配置),分别创建 ZipkinConsumerDelegate 和 ZipkinProviderDelegate 对象,用于ZipkinTracingHandler 追踪处理调用链请求。

总结

至此,ServiceComb如何一步一步去实现zipkin分布式调用链追踪,已经解读完毕。

参考


  1. zipkin ↩︎

  2. Distributed Tracing with ServiceComb and Zipkin ↩︎ ↩︎

  3. java-chassiss使用手册——调用参考 ↩︎

你可能感兴趣的:(ServiceComb + Zipkin : 源码解读)