微服务之分布式跟踪系统(springboot+zipkin)

一、zipkin是什么

        zipkin是一个开放源代码分布式的跟踪系统,由Twitter公司开源,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。它的理论模型来自于Google Dapper 论文。

        每个服务向zipkin报告计时数据,zipkin会根据调用关系通过Zipkin UI生成依赖关系图,显示了多少跟踪请求通过每个服务,该系统让开发者可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈。

二、什么需要分布式跟踪系统(zipkin)

        当代的互联网的服务,通常都是用复杂的、大规模分布式集群来实现的。特别是随着微服务架构和容器技术的兴起(加速企业敏捷,快速适应业务变化,满足架构的高可用和高扩展),互联网应用往往构建在不同的服务之上,这些服务,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要一些可以帮助理解系统行为、用于快速分析性能问题的工具。先是Google开发其分布式跟踪系统并且发表了Dapper 论文,然后由Twitter参照Dapper论文设计思想开发zipkin分布式跟踪系统,同时开源出来。

         zipkin通过采集跟踪数据可以帮助开发者深入了解在分布式系统中某一个特定的请求时如何执行的。假如说,我们现在有一个用户请求超时,我们就可以将这个超时的请求调用链展示在UI当中。我们可以很快度的定位到导致响应很慢的服务究竟是什么。如果对这个服务细节也很很清晰,那么我们还可以定位是服务中的哪个问题导致超时。同时,通过服务调用链路能够快速定位系统的性能瓶颈。

三、zipkin下载与启动

       在本节中,我们将介绍下载和启动zipkin实例,以便在本地检查zipkin。有三种安装方法:使用官网自己打包好的Jar运行,Docker方式或下载源代码自己打包Jar运行(因为zipkin使用了springboot,内置了服务器,所以可以直接使用jar运行)。zipkin推荐使用docker方式运行,我后面会专门写一遍关于docker的运行方式,而源码运行方式好处是有机会体验到最新的功能特性,但是可能也会带来一些比较诡异的坑,所以不做讲解,下面我直接是使用官网打包的jar运行过程:

(1)    下载jar文件

[python]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. wget -O zipkin.jar  'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'  

      不过,我在运行的过程中,发现无法下载。然后我通过翻墙软件,下载了其最新的jar文件(zipkin-server-1.17.1-exec.jar),我这里也提供其下载地址。

(2)    启动实例

         Java-jar zipkin-server-1.17.1-exec.jar或者java -jar zipkin.jar(注意需要安转JDK8或者以上的版本),启动成功如下图所示:


(3)    查看运行效果

        通过上图,我们发现zipkin使用springboot,并且启动的端口为9411,然后我们通过浏览器访问,效果如下:


四、zipkin的架构与核心概念

        将数据发送到zipkin的已检测应用程序中的组件称为Reporter。它通过几种传输方式之一将跟踪数据发送到zipkin收集器,zipkin收集器将跟踪数据保存到存储器。稍后,存储由API查询以向UI提供数据。为了保证服务的调用链路跟踪,zipkin使用传输ID,例如,当正在进行跟踪操作并且它需要发出传出http请求时,会添加一些headers信息以传播ID,但它不能用于发送详细信息(操作名称、数据等)。其架构图如下所示:


A、Span

        基本工作单元,一次链路调用创建一个span,通过一个64位ID标识它,span通过还有其他的数据,例如描述信息,时间戳,key-value对的(Annotation)tag信息,parent-id等,其中parent-id 可以表示span调用链路来源,通俗的理解span就是一次请求信息。

B、 Trace

      类似于树结构的Span集合,表示一条调用链路,存在唯一标识。

C、 Annotation

      注解,用来记录请求特定事件相关信息(例如时间),通常包含四个注解信息:

(1)cs - ClientStart,表示客户端发起请求

(2)sr - Server Receive,表示服务端收到请求

(3)ss - Server Send,表示服务端完成处理,并将结果发送给客户端

(4)cr - Client Received,表示客户端获取到服务端返回信息

D、 Transport

        收集被trace的services的spans,并且传输给zipkin的collector,有三个主要传输:HTTP,Kafka和Scribe。

E、 Collector

       zipkincollector会对一个到来的被trace的数据(span)进行验证、存储并设置索引。

F、 Storage

        存储,zipkin默认的存储方式为in-memory,即不会进行持久化操作。如果想进行收集数据的持久化,可以存储数据在Cassandra,因为Cassandra是可扩展的,有一个灵活的模式,并且在Twitter中被大量使用,我们使这个组件可插入。除了Cassandra,我们原生支持ElasticSearch和MySQL。其他后端可能作为第三方扩展提供。

G、QueryService

        一旦数据被存储和索引,我们需要一种方法来提取它。查询守护程序提供了一个用于查找和检索跟踪的简单JSON API,此API的主要使用者是WebUI。

H、 WebUI

        展示页面,提供了一个漂亮的界面来查看痕迹。 Web UI提供了一种基于服务,时间和注释查看trace的方法(通过Query Service)。注意:在UI中没有内置的身份验证。

五、分布式跟踪系统实践(springboot+zipkin)

  5.1场景设置与分析

      现在有一个服务A调用服务B,服务B又分别调用服务C和D,整个链路过程的关系图如下所示:


其调用工作流程调用链路详细图:


上图表示一请求链路,一条链路通过TraceId唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来,parentId==null,表示该span就是root span,如下图所示:

 

zipkin提供了各种语言的客户端(Java、GoScala、Ruby、JavaScript),使用它们想zipkin汇报数据。

5.2 代码编写

     下面我以Java的客户端Brave为例完成上面四个服务调用代码编写,源代码下载地址:https://github.com/dreamerkr/mircoservice.git文件夹springboot+zipkin下面,具体如下:

(1)    serivce1

a、 springboot启动类

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <span style="font-size:14px;">@SpringBootApplication  
  2. @EnableAutoConfiguration  
  3. public class Application {  
  4.   
  5.     public static void main(String[] args) {  
  6.         SpringApplication.run(Application.class, args);  
  7.     }  
  8. }</span>  

b、zipkin收集与配置类

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  *  
  3.  * TODO zipkin配置 
  4.  * 
  5.  * @author wangzhao (mailto:wangzhao@primeton.com) 
  6.  */  
  7. @Configuration  
  8. public class ZipkinConfig {  
  9.       
  10.     //span(一次请求信息或者一次链路调用)信息收集器  
  11.     @Bean  
  12.     public SpanCollector spanCollector() {  
  13.         Config config = HttpSpanCollector.Config.builder()  
  14.                 .compressionEnabled(false)// 默认false,span在transport之前是否会被gzipped  
  15.                 .connectTimeout(5000)  
  16.                 .flushInterval(1)  
  17.                 .readTimeout(6000)  
  18.                 .build();  
  19.         return HttpSpanCollector.create("http://localhost:9411", config, new EmptySpanCollectorMetricsHandler());  
  20.     }  
  21.       
  22.     //作为各调用链路,只需要负责将指定格式的数据发送给zipkin  
  23.     @Bean  
  24.     public Brave brave(SpanCollector spanCollector){  
  25.         Builder builder = new Builder("service1");//指定serviceName  
  26.         builder.spanCollector(spanCollector);  
  27.         builder.traceSampler(Sampler.create(1));//采集率  
  28.         return builder.build();  
  29.     }  
  30.   
  31.   
  32.     //设置server的(服务端收到请求和服务端完成处理,并将结果发送给客户端)过滤器  
  33.     @Bean  
  34.     public BraveServletFilter braveServletFilter(Brave brave) {  
  35.         BraveServletFilter filter = new BraveServletFilter(brave.serverRequestInterceptor(),  
  36.                 brave.serverResponseInterceptor(), new DefaultSpanNameProvider());  
  37.         return filter;  
  38.     }  
  39.       
  40.     //设置client的(发起请求和获取到服务端返回信息)拦截器  
  41.     @Bean  
  42.     public OkHttpClient okHttpClient(Brave brave){  
  43.         OkHttpClient httpClient = new OkHttpClient.Builder()  
  44.                 .addInterceptor(new BraveOkHttpRequestResponseInterceptor(  
  45.                         brave.clientRequestInterceptor(),  
  46.                         brave.clientResponseInterceptor(),   
  47.                         new DefaultSpanNameProvider())).build();  
  48.         return httpClient;  
  49.     }  
  50. }  

c、 服务1业务代码

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Api("service的API接口")  
  2. @RestController  
  3. @RequestMapping("/service1")  
  4. public class ZipkinBraveController {  
  5.   
  6.   
  7.     @Autowired  
  8.     private OkHttpClient client;  
  9.       
  10.     @ApiOperation("trace第一步")  
  11.     @RequestMapping("/test")  
  12.     public String service1() throws Exception {  
  13.         Thread.sleep(100);  
  14.         Request request = new Request.Builder().url("http://localhost:8082/service2/test").build();  
  15.         Response response = client.newCall(request).execute();  
  16.         return response.body().string();  
  17.     }  
  18.       
  19. }  

d、pom文件

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1.   <dependencies>  
  2.           <dependency>  
  3.             <groupId>org.springframework.boot</groupId>  
  4.             <artifactId>spring-boot-starter</artifactId>  
  5.             <version>1.3.5.RELEASE</version>  
  6.         </dependency>  
  7.         <dependency>  
  8.             <groupId>org.springframework.boot</groupId>  
  9.             <artifactId>spring-boot-starter-web</artifactId>  
  10.             <version>1.3.5.RELEASE</version>  
  11.         </dependency>  
  12.        <dependency>  
  13.              <groupId>io.zipkin.brave</groupId>  
  14.               <artifactId>brave-core</artifactId>  
  15.               <version>3.9.0</version>  
  16.           </dependency>  
  17. <!--      <dependency>  
  18.              <groupId>io.zipkin.reporter</groupId>  
  19.              <artifactId>zipkin-reporter-urlconnection</artifactId>  
  20.              <version>0.2.0</version>  
  21.           </dependency>  
  22.           -->  
  23.           <dependency>  
  24.               <groupId>io.zipkin.brave</groupId>  
  25.               <artifactId>brave-spancollector-http</artifactId>  
  26.              <version>3.9.0</version>  
  27.          </dependency>  
  28.          <dependency>  
  29.              <groupId>io.zipkin.brave</groupId>  
  30.               <artifactId>brave-web-servlet-filter</artifactId>  
  31.               <version>3.9.0</version>  
  32.          </dependency>  
  33.          <dependency>  
  34.              <groupId>io.zipkin.brave</groupId>  
  35.              <artifactId>brave-okhttp</artifactId>  
  36.              <version>3.9.0</version>  
  37.          </dependency>  
  38.   
  39.         <dependency>  
  40.             <groupId>io.springfox</groupId>  
  41.             <artifactId>springfox-swagger2</artifactId>  
  42.             <version>2.6.1</version>  
  43.         </dependency>  
  44.         <dependency>  
  45.             <groupId>io.springfox</groupId>  
  46.             <artifactId>springfox-swagger-ui</artifactId>  
  47.             <version>2.6.1</version>  
  48.         </dependency>  
  49.   </dependencies>  
e、application.properties

[plain]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. application.name: service1  
  2.   
  3. server.port: 8081  

(2)    serivce2

            pom文件和启动类与service1是一样的,配置类ZipkinConfig把service1改成service2,application.properties改name为service2、改端口为8082,服务2业务代码如下

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Api("service的API接口")  
  2. @RestController  
  3. @RequestMapping("/service2")  
  4. public class ZipkinBraveController {  
  5.   
  6.     @Autowired  
  7.     private OkHttpClient client;  
  8.       
  9.     @ApiOperation("trace第二步")  
  10.     @RequestMapping("/test")  
  11.     public String service1() throws Exception {  
  12.         Thread.sleep(200);  
  13.         Request request3 = new Request.Builder().url("http://localhost:8083/service3/test").build();  
  14.         Response response3 = client.newCall(request3).execute();  
  15.           
  16.         Request request4 = new Request.Builder().url("http://localhost:8084/service4/test").build();  
  17.         Response response4 = client.newCall(request4).execute();  
  18.         return response3.toString()+":"+response4.toString();  
  19.     }  
  20.       
  21. }  

(3)    serivce3

          pom文件和启动类与service1是一样的,配置类ZipkinConfig把service1改成service3,application.properties改name为service3、改端口为8083,服务3业务代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Api("service的API接口")  
  2. @RestController  
  3. @RequestMapping("/service3")  
  4. public class ZipkinBraveController {  
  5.       
  6.     @ApiOperation("trace第三步")  
  7.     @RequestMapping("/test")  
  8.     public String service1() throws Exception {  
  9.         Thread.sleep(300);  
  10.         return "service3";  
  11.     }  
  12.       
  13. }  
  14. }  

(4)    serivce4

           pom文件和启动类与service1是一样的,配置类ZipkinConfig把service1改成service4,application.properties改name为service4、改端口为8084,服务4业务代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Api("service的API接口")  
  2. @RestController  
  3. @RequestMapping("/service4")  
  4. public class ZipkinBraveController {  
  5.       
  6.     @ApiOperation("trace第四步")  
  7.     @RequestMapping("/test")  
  8.     public String service1() throws Exception {  
  9.         Thread.sleep(300);  
  10.         return "service4";  
  11.     }  
  12.       
  13. }  

5.3运行效果

 (1)分别启动每个服务,然后访问服务1,浏览器访问(http://localhost:8081/service1/test

(2)输入zipkin地址,每次trace的列表


点击其中的trace,可以看trace的树形结构,包括每个服务所消耗的时间:


点击每个span可以获取延迟信息:


同时可以查看服务之间的依赖关系:


        本篇主要与大家分享zipkin进行简单的服务分布式跟踪,但是收集的数据都在内存里面,重新启动后数据丢失,但是zipkin还提供了Cassandra、mysql、ElasticSearch等多种存储方式,下面章节做讲解《微服务之分布式跟踪系统(springboot+zipkin+mysql)

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐