前言

传统单机系统在使用过程中,如果某个请求响应过慢或是响应出错,开发人员可以清楚知道某个请求出了问题,查看日志可以定位到具体方法。但是在分布式系统中,倘若客户端一个请求到达服务器后,由多个服务协作完成。比如:服务 A 调用服务 B,服务 B 又调用服务 C 和服务 D,服务 D 又调用服务 E,那么想要知道是哪个服务处理时间过长或是处理异常导致这个请求响应缓慢或中断的话,就需要开发人员一个服务接一个服务的去机器上查看日志,先定位到出问题的服务,再定位出问题的具体地方。试想一下,随着系统越来越壮大,服务越来越多,一个请求对应处理的服务调用链越来越长,这种排查方式何其艰难。为了解决这种问题,便诞生了各种分布式场景中追踪问题的解决方案,zipkin 就是其中之一。

整体结构长啥样

一个独立的分布式追踪系统,客户端存在于应用中(即各服务中),应具备追踪信息生成、采集发送等功能,而服务端应该包含以下基本的三个功能:

  • 信息收集:用来收集各服务端采集的信息,并对这些信息进行梳理存储、建立索引。
  • 数据存储:存储追踪数据。
  • 查询服务:提供查询请求链路信息的接口。

zipkin 整体结构图如下:

zipkin 结构图

zipkin(服务端) 包含四个组件,分别是 collector、storage、search、web UI。

  • collector 就是信息收集器, 作为一个守护进程,它会时刻等待客户端传递过来的追踪数据,对这些数据进行验证、存储以及创建查询需要的索引。
  • storage 是存储组件。zipkin 默认直接将数据存在内存中,此外支持使用 Cassandra、ElasticSearch 和 Mysql。
  • search 是一个查询进程,它提供了简单的 JSON API 来供外部调用查询。
  • web UI 是 zipkin 的服务端展示平台,主要调用 search 提供的接口,用图表将链路信息清晰地展示给开发人员。

zipkin 的客户端主要负责根据应用的调用情况生成追踪信息,并且将这些追踪信息发送至 zipkin 由收集器接收。各语言支持均不同,具体可以查看zipkin 官网,java 语言的支持就是 brave。上面结构图中,有追踪器就是指集成了 brave。

基本概念了解下

在使用 zipkin 之前,先了解一下 Trace 和 Span 这两个基本概念。一个请求到达应用后所调用的所有服务所有服务组成的调用链就像一个树结构(如下图),我们追踪这个调用链路得到的这个树结构可以称之为Trace

在一次 Trace 中,每个服务的 每一次调用,就是一个 基本工作单元,就像上图中的每一个树节点,称之为 span。每一个 span 都有一个 id 作为唯一标识,同样每一次 Trace 都会生成一个 traceId 在 span 中作为追踪标识,另外再通过一个 parentId 标明本次调用的发起者(就是发起者的 span-id)。当 span 有了上面三个标识后,就可以很清晰的将多个 span 进行梳理串联,最终归纳出一条完整的跟踪链路。此外,span 还会有其他数据,比如:名称、节点上下文、时间戳以及 K-V 结构的 tag 信息等等(Zipkin v1 核心注解如“cs”和“sr”已被 Span.Kind 取代,详情查看 zipkin-api,本文会在入门的 demo 介绍完后对具体的 Span 数据模型进行说明)。

具体怎么追踪的

追踪器位于应用程序上,负责生成相关 ID、记录 span 需要的信息,最后通过传输层传递给服务端的收集器。我们首先思考下面几个问题:

  • 每个 span 需要的基本信息何时生成?
  • 哪些信息需要随着服务调用传递给服务提供方?
  • 什么时候发送 span 至 zipkin 服务端?
  • 以何种方式发送 span?

一个 span 表示一次服务调用,那么追踪器必定是被服务调用发起的动作触发,生成基本信息,同时为了追踪服务提供方对其他服务的调用情况,便需要传递本次追踪链路的 traceId 和本次调用的 span-id。服务提供方完成服务将结果响应给调用方时,需要根据调用发起时记录的时间戳与当前时间戳计算本次服务的持续时间进行记录,至此这次调用的追踪 span 完成,就可以发送给 zipkin 服务端了。但是需要注意的是,发送 span 给 zipkin collector 不得影响此次业务结果,其发送成功与否跟业务无关,因此这里需要采用异步的方式发送,防止追踪系统发送延迟与发送失败导致用户系统的延迟与中断。下图就表示了一次 http 请求调用的追踪流程(基于 zipkin 官网提供的流程图):

一次http请求调用的追踪流程 可以看出服务 A 请求服务 B 时先被追踪器拦截,记录 tag 信息、时间戳,同时将追踪标识添加进 http header 中传递给服务 B,在服务 B 响应后,记录持续时间,最终采取异步的方式发送给 zipkin 收集器。span 从被追踪的服务传送到 Zipkin 收集器有三种主要的传送方式:http、Kafka 以及 Scribe(Facebook 开源的日志收集系统)。

1 分钟安装 zipkin

上文对基于 zipkin 实现分布式追踪系统的原理做了全面的说明,这里简单介绍一下 zipkin 的安装方法,下载 jar 包,直接运行。简单粗暴,但要注意必须 jdk1.8 及以上。其余两种安装方式见官方介绍。

wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
java -jar zipkin.jar
复制代码

启动成功后,打开浏览器访问 zipkin 的 webUI,输入 http://ip:9411/, 显示页面如下图。具体使用后面介绍。

写个 Demo 用起来(Spring Boot 整合 zipkin)

java 版客户端 Brave 的官方文档很少,都在 github 里。小白当时找的那叫个头疼啊,网上各路大神写的博客中的代码你扒下来换最新的依赖后都会显示那些类被标记为过时,不建议使用。

  • brave 源码地址:github.com/openzipkin/…
  • 官方 demo 地址:github.com/openzipkin/…
  • 友情提示:本节代码较多,注释还算详细,介绍文字偏少。 小白写的 demo 结构如下图,分别创建了 service1、service2、service3 三个 boot 应用,将 brave 整合部分单独作为一个 module,这样可以嵌入服务中复用,避免重复编码。

maven 依赖(zipkin_client)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
&lt;groupId&gt;com.ycg&lt;/groupId&gt;
&lt;artifactId&gt;zipkin_client&lt;/artifactId&gt;
&lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
&lt;name&gt;zipkin_client&lt;/name&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;<span class="hljs-built_in">source</span>&gt;6&lt;/<span class="hljs-built_in">source</span>&gt;
                &lt;target&gt;6&lt;/target&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
&lt;packaging&gt;jar&lt;/packaging&gt;

&lt;properties&gt;
    &lt;java.version&gt;1.8&lt;/java.version&gt;
    &lt;spring-boot.version&gt;2.1.1.RELEASE&lt;/spring-boot.version&gt;
    &lt;brave.version&gt;5.6.0&lt;/brave.version&gt;
&lt;/properties&gt;

&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
            &lt;artifactId&gt;spring-boot-dependencies&lt;/artifactId&gt;
            &lt;version&gt;<span class="hljs-variable">${spring-boot.version}</span>&lt;/version&gt;
            &lt;<span class="hljs-built_in">type</span>&gt;pom&lt;/<span class="hljs-built_in">type</span>&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
            &lt;artifactId&gt;brave-bom&lt;/artifactId&gt;
            &lt;version&gt;<span class="hljs-variable">${brave.version}</span>&lt;/version&gt;
            &lt;<span class="hljs-built_in">type</span>&gt;pom&lt;/<span class="hljs-built_in">type</span>&gt;
            &lt;scope&gt;import&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.httpcomponents&lt;/groupId&gt;
        &lt;artifactId&gt;httpclient&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- zipkin客户端依赖 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
        &lt;artifactId&gt;brave&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.reporter2&lt;/groupId&gt;
        &lt;artifactId&gt;zipkin-sender-okhttp3&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- 添加记录MVC的类、方法名到span的依赖 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
        &lt;artifactId&gt;brave-instrumentation-spring-webmvc&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- 添加brave的httpclient依赖 --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
        &lt;artifactId&gt;brave-instrumentation-httpclient&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- 集成Brave上下文的<span class="hljs-built_in">log</span> --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;io.zipkin.brave&lt;/groupId&gt;
        &lt;artifactId&gt;brave-context-slf4j&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

</project>

复制代码

配置类编写(zipkin_client)

package com.ycg.zipkin_client;

import brave.CurrentSpanCustomizer;
import brave.SpanCustomizer;
import brave.Tracing;
import brave.context.slf4j.MDCScopeDecorator;
import brave.http.HttpTracing;
import brave.httpclient.TracingHttpClientBuilder;
import brave.propagation.B3Propagation;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.ThreadLocalCurrentTraceContext;
import brave.servlet.TracingFilter;
import brave.spring.webmvc.SpanCustomizingAsyncHandlerInterceptor;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import zipkin2.Span;
import zipkin2.reporter.AsyncReporter;
import zipkin2.reporter.Sender;
import zipkin2.reporter.okhttp3.OkHttpSender;

import javax.servlet.Filter;

/**
* 针对 mvc controller 和 restTemplate 的 zipkin 客户端配置
*/
@Configuration
@Import(SpanCustomizingAsyncHandlerInterceptor.class)
public class ZipkinClientConfiguration implements WebMvcConfigurer {

/**
 * 配置如何向 zipkin 发送 span
 */
@Bean
Sender <span class="hljs-function"><span class="hljs-title">sender</span></span>() {
    // 注意这里更换为自己安装的zipkin所在的主机IP
    <span class="hljs-built_in">return</span> OkHttpSender.create(<span class="hljs-string">"http://10.150.27.36:9411/api/v2/spans"</span>);
}

/**
 * 配置如何把 span 缓冲到给 zipkin 的消息
 */
@Bean
AsyncReporter&lt;Span&gt; <span class="hljs-function"><span class="hljs-title">spanReporter</span></span>() {
    <span class="hljs-built_in">return</span> AsyncReporter.create(sender());
}

/**
 * 配置跟踪过程中的Trace信息
 */
@Bean
Tracing tracing(@Value(<span class="hljs-string">"<span class="hljs-variable">${spring.application.name}</span>"</span>) String serviceName) {
    <span class="hljs-built_in">return</span> Tracing.newBuilder()
            .localServiceName(serviceName)  // 设置节点名称
            .propagationFactory(ExtraFieldPropagation.newFactory(B3Propagation.FACTORY, <span class="hljs-string">"user-name"</span>))
            .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder()
                    .addScopeDecorator(MDCScopeDecorator.create()) // puts trace IDs into logs
                    .build()
            )
            .spanReporter(spanReporter()).build();
}

/** 注入可定制的Span */
@Bean
SpanCustomizer spanCustomizer(Tracing tracing) {
    <span class="hljs-built_in">return</span> CurrentSpanCustomizer.create(tracing);
}

/** 决定如何命名和标记span。 默认情况下,它们的名称与http方法相同 */
@Bean
HttpTracing httpTracing(Tracing tracing) {
    <span class="hljs-built_in">return</span> HttpTracing.create(tracing);
}

/** 导入过滤器,该过滤器中会为http请求创建span */
@Bean
Filter tracingFilter(HttpTracing httpTracing) {
    <span class="hljs-built_in">return</span> TracingFilter.create(httpTracing);
}

/**
 * 导入 zipkin 定制的 RestTemplateCustomizer
 */
@Bean
RestTemplateCustomizer useTracedHttpClient(HttpTracing httpTracing) {
    final CloseableHttpClient httpClient = TracingHttpClientBuilder.create(httpTracing).build();
    <span class="hljs-built_in">return</span> new <span class="hljs-function"><span class="hljs-title">RestTemplateCustomizer</span></span>() {
        @Override public void customize(RestTemplate restTemplate) {
            restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
        }
    };
}

@Autowired
SpanCustomizingAsyncHandlerInterceptor webMvcTracingCustomizer;

/** 使用应用程序定义的Web标记装饰服务器span */
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(webMvcTracingCustomizer);
}

}

复制代码

boot 服务模块

  1. maven 依赖:boot+web 起步依赖,另引入上面封装的 zipkin_client 模块依赖。
<dependency>
    <groupId>com.ycg</groupId>
    <artifactId>zipkin_client</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
复制代码
  1. 启动类导入 zipkin_client 模块 的配置类 ZipkinClientConfiguration
@SpringBootApplication
@Import(ZipkinClientConfiguration.class)
public class Service1Application {
    public static void main(String[] args) {
        SpringApplication.run(Service1Application.class, args);
    }
}
复制代码
  1. 编写 Controller,service2 和 service3 的代码类似。由于 zipkin 配置类那边向 IOC 容器注入 zipkin 定制的 RestTemplateCustomizer, 注意这里使用注入的 RestTemplateBuilder 创建 restTemplate
@EnableAutoConfiguration
@RestController
public class Service1Controller {
private RestTemplate restTemplate;

@Autowired Service1Controller(RestTemplateBuilder restTemplateBuilder) {
    this.restTemplate = restTemplateBuilder.build();
}

@GetMapping(value = <span class="hljs-string">"/service1"</span>)
public String <span class="hljs-function"><span class="hljs-title">getService</span></span>() {
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    <span class="hljs-built_in">return</span> <span class="hljs-string">"service1 sleep 100ms -&gt;"</span> + restTemplate.getForObject(<span class="hljs-string">"http://localhost:8882/service2"</span>,String.class);
}

}

复制代码
  1. 设置三个 boot 服务的内置 tomcat 端口号分别为 8881、8882、8883。

启动验证

到这里,就完成了一个 springboot 整合 zipkin 简单的 demo,分别启动三个 boot 应用后,在浏览器访问 http://localhost:8881/service1,浏览器显示如下图:

打开 zipkin-webUI, 点击查询,便可以查到刚才请求的追踪链路,如下图。

继续点击查到的链路信息,便可查看该条追踪链路的详细信息。这里采用缩进的形式展示了整条调用链路,并且再每个调用后表明了所花费时间。点击右上角 json 按钮,便能看到本次 trace 的 json 数据。

span 数据结构详解

json 结构概览与各字段含义

一次追踪链路会包含很多个 span, 因此一个 trace 便是一个数组,其标准的 json 结构如下:

[
  {
    "traceId": "string",    // 追踪链路ID
    "name": "string",       // span名称,一般为方法名称
    "parentId": "string",   // 调用者ID
    "id": "string",         // spanID
    "kind": "CLIENT",       // 替代zipkin v1的注解中的四个核心状态,详细介绍见下文
    "timestamp": 0,         // 时间戳,调用时间
    "duration": 0,          // 持续时间-调用的服务所消耗的时间
    "debug": true,          
    "shared": true,
    "localEndpoint": {      // 本地网络节点上下文
      "serviceName": "string",
      "ipv4": "string",
      "ipv6": "string",
      "port": 0
    },
    "remoteEndpoint": {    // 远端网络节点上下文
      "serviceName": "string",
      "ipv4": "string",
      "ipv6": "string",
      "port": 0
    },
    "annotations": [      // value通常是缩写代码,对应的时间戳表示代码标记事件的时间
      {
        "timestamp": 0,
        "value": "string"
      }
    ],
    "tags": {        // span的上下文信息,比如:http.method、http.path
      "additionalProp1": "string",
      "additionalProp2": "string",
      "additionalProp3": "string"
    }
  }
]
复制代码

聊聊 annotation 和 kind 的前世姻缘

zipkin V1 之 Annotation V1 时 Annotation 用于记录一个事件,事件由 value 标识,事件发生时间则记录对应的时间戳。一些核心注解核心注解用于定义一个请求的开始和结束。主要是如下四种注解:

  • cs - Client Send,表示客户端发起请求.
  • sr - Server Receive,表示服务端收到请求。使用 sr 的时间减去 cs 的时间便可得到网络传输的时间。
  • ss - Server Send,表示服务端完成处理,并将结果发送给客户端。使用 ss 的时间减去 sr 的时间便是服务端处理请求的时间。
  • cr - Client Received,表示客户端获取到服务端返回信息。使用 cr 的时间减去 cs 的时间便是整个请求所消耗的时间。

zipkin V2 之 Kind V2 使用 Span.Kind 替代了 V1 的几个表示请求开始与结束的核心注解。kind 一共有四种状态,其为不同状态时,timestamp、duration、remoteEndpoint 代表的意义均不相同。

  • CLIENT:

    timestamp 是请求被发送的时刻,相当于 v1 中注解 cs。
    duration 代表发送请求后,接收到服务端响应前的持续时间,也就是整个请求所消耗的时间。
    remoteEndpoint 表示被调用方的网络节点信息。

  • SERVER:

    timestamp 是服务端接到请求并准备开始处理它的时间,相当于 v1 中的 sr。
    duration 代表服务端接到请求后、发送响应前的持续时间,也就是服务端的净处理时间。
    remoteEndpoint 表示调用方的网络节点信息。

  • PRODUCER:

    timestamp 是消息被发送的时刻。
    duration 代表发送方发送后,消息队列结束到消息前的延迟时间,比如批处理的场景。
    remoteEndpoint 表示消息队列的网络节点信息。

  • CONSUMER:

    timestamp 是消息被消息队列接收到的时刻。
    duration 代表消息被消息队列接收到,被消费者消费前的持续时间,比如消息积压的场景。
    remoteEndpoint 表示消费者节点信息,未知则表示 service name。

V1 针对消息队列也有 ms、mr 等注解,这里就不再详细介绍了。小白觉得 kind 这种替换后,整个追踪链路更为清晰直观,或许这也是 zipkin 的考虑之一吧。

再看 Demo 中追踪链路的 JSON 数据

相信看到这里的小伙伴回头再看 demo 中链路的 json 数据,应该可以明白具体的意思了。小白这里再梳理一下。追踪链路的 JSON 数据如下(建议直接跳过数据看下面分析):

[
  {
    "traceId": "3857b4a56c99e9f8",
    "parentId": "7dd11a047eb02622",
    "id": "e5427222edb62a7c",
    "kind": "SERVER",
    "name": "get /service3",
    "timestamp": 1547458424863333,
    "duration": 409599,
    "localEndpoint": {
      "serviceName": "server3",
      "ipv4": "172.30.22.138"
    },
    "remoteEndpoint": {
      "ipv4": "127.0.0.1",
      "port": 52845
    },
    "tags": {
      "http.method": "GET",
      "http.path": "/service3",
      "mvc.controller.class": "Service3Controller",
      "mvc.controller.method": "getService"
    },
    "shared": true
  },
  {
    "traceId": "3857b4a56c99e9f8",
    "parentId": "7dd11a047eb02622",
    "id": "e5427222edb62a7c",
    "kind": "CLIENT",
    "name": "get",
    "timestamp": 1547458424756985,
    "duration": 520649,
    "localEndpoint": {
      "serviceName": "server2",
      "ipv4": "172.30.22.138"
    },
    "tags": {
      "http.method": "GET",
      "http.path": "/service3"
    }
  },
  {
    "traceId": "3857b4a56c99e9f8",
    "parentId": "3857b4a56c99e9f8",
    "id": "7dd11a047eb02622",
    "kind": "SERVER",
    "name": "get /service2",
    "timestamp": 1547458424446556,
    "duration": 880044,
    "localEndpoint": {
      "serviceName": "server2",
      "ipv4": "172.30.22.138"
    },
    "remoteEndpoint": {
      "ipv4": "127.0.0.1",
      "port": 52844
    },
    "tags": {
      "http.method": "GET",
      "http.path": "/service2",
      "mvc.controller.class": "Service2Controller",
      "mvc.controller.method": "getService"
    },
    "shared": true
  },
  {
    "traceId": "3857b4a56c99e9f8",
    "parentId": "3857b4a56c99e9f8",
    "id": "7dd11a047eb02622",
    "kind": "CLIENT",
    "name": "get",
    "timestamp": 1547458424271786,
    "duration": 1066836,
    "localEndpoint": {
      "serviceName": "server1",
      "ipv4": "172.30.22.138"
    },
    "tags": {
      "http.method": "GET",
      "http.path": "/service2"
    }
  },
  {
    "traceId": "3857b4a56c99e9f8",
    "id": "3857b4a56c99e9f8",
    "kind": "SERVER",
    "name": "get /service1",
    "timestamp": 1547458424017344,
    "duration": 1358590,
    "localEndpoint": {
      "serviceName": "server1",
      "ipv4": "172.30.22.138"
    },
    "remoteEndpoint": {
      "ipv6": "::1",
      "port": 52841
    },
    "tags": {
      "http.method": "GET",
      "http.path": "/service1",
      "mvc.controller.class": "Service1Controller",
      "mvc.controller.method": "getService"
    }
  }
]
复制代码

我们从下往上看,这才是请求最开始的地方。首先看最下面的 span(3857b4a56c99e9f8)。请求(http://localhost:8881)是由浏览器发出, 那么当请求到达服务 1 时,作为服务端便会生成 kind 为 SERVER 的 span,其中 duration 便是本次请求到后端后的净处理时间,localEndpoint 是 server1 的节点信息,remoteEndpoint 的调用方也就是浏览器的节点信息。

接着服务 1 需要调用服务 2 的服务,这时服务 1 是作为客户端发出请求的。因此会记录出从下往上第二个 span(7dd11a047eb02622), 一个客户端 span, 也就是 kind=CLIENT。localEndpoint 还是自己,同时 tag 里添加了发出的请求信息,duration 表示发出 /service2 的请求后,到接收到 server2 的响应所消耗的时间。再往上 span(7dd11a047eb02622),就是 server2 接收到 server1 的请求后记录的 SERVER span。剩下的同理,小白就不多说了。

结束语

到这里小白就介绍完了基于 zipkin 实现分布式追踪系统的基本原理与实现,当然这只是一个入门,追踪信息是全量收集还是采样收集,设置什么样的采样频率,异步发送 span 使用 http 还是 kafka,这些问题都是需要在生产环境中根据实际场景综合考量的。就本文而言,小白觉得只要你仔细阅读了,认真思考了,一定还是收获不少的,当然有深入研究的小伙伴除外。后续小白会深入 Brave 的源码了解具体的追踪实现,如有错误,也请多多拍砖多多交流。另,画图、码字、梳理知识不易,如要转载,请注明出处

参考

zipkin 官网

zipkin-api

官方示例:brave-webmvc-example

  • Java

    Java,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台的总称。用 Java 实现的 HotJava 浏览器(支持 Java applet)显示了 Java 的魅力:跨平台、动态的…

    380 引用 • 6 回帖
感谢    赞同    分享    收藏    关注    反对    举报    ...