架构设计(12) --分布式链路跟踪:Zipkin实践

最近升级改造我们链路跟踪系统Log2,花了将近一周时间调研一些开源的链路跟踪系统,在此调研过程中,做了一些笔记和总结分享出来,若有误请指教。

《分布式链路跟踪1: 理论知识》

《分布式链路跟踪2:Zipkin实践》

《分布式链路跟踪3:skywalking原理和实践》

《分布式链路跟踪4:自研组件Log2》

一、Zipkin是什么


         《架构设计(12) 分布式链路跟踪》提到,Google的Dapper论文,介绍了如何进行服务追踪分析。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以可视化地分析服务调用链路和服务间的依赖关系。

          Zipkin是基于 Dapper 论文实现,由Twitter公司开发并开源一个分布式的跟踪系统。Zipkin支持多种语言包括JavaScript,Python,Java, Scala, Ruby, C#, Go等。其中Java由多种不同的库来支持。

         官网:https://github.com/openzipkin/zipkin/

 

二、Zipkin基本概念


1、trace:请求跟踪树

Zipkin使用Trace结构表示一次请求的跟踪,从请求到达跟踪系统的边界开始,到被跟踪系统返回响应为止的过程。一次请求可能由后台的若干服务负责处理,每个服务的处理是一个Span,Span之间有依赖关系,Trace就是树结构的Span集合;.包含一系列的span,它们组成了一个树型结构.

2、span: 服务跟踪记录

每个 trace中会调用若干个服务,为了记录调用了哪些服务,以及每次调用的消耗时间等信息,在每次调用服务时,埋入一个调用记录,称为一个“span”。

span是基本的工作单元,包含了一些描述信息:id,parentId,name,timestamp,duration,annotations等
最开始的初始Span称为根span,此span中span id和 trace id值相同。

3、annotations标注标注时间点发生的Event

标注用于及时记录事件;有一组核心注释用于定义RPC请求的开始和结束;

  • cs:Client Send,客户端发起请求;
  • sr:Server Receive,服务器接受请求,开始处理;
  • ss:Server Send,服务器完成处理,给客户端应答;
  • cr:Client Receive,客户端接受应答从服务器;

4、binaryAnnotations额外信息

旨在提供有关RPC的额外信息,可以存放用户自定义信息,比如:sessionID、userID、userIP、异常等。

 

trace信息例子

一个完成trace信息如下:

traceId:标记一次请求的跟踪,相关的Spans都有相同的traceId;
id:span id;
name:span的名称,一般是接口方法的名称;
parentId:可选的id,当前Span的父Span id,通过parentId来保证Span之间的依赖关系,如果没有parentId,表示当前Span为根Span;
timestamp:Span创建时的时间戳,使用的单位是微秒(而不是毫秒),所有时间戳都有错误,包括主机之间的时钟偏差以及时间服务重新设置时钟的可能性,出于这个原因,Span应尽可能记录其duration;
duration:持续时间使用的单位是微秒(而不是毫秒);

{
    "traceId": "423f62ed10f826bd",
    "id": "09d324b8d0563b0c",
    "name": "http:/hello",
    "parentId": "423f62ed10f826bd",
    "timestamp": 1593490361270000,
    "duration": 19000,
    "annotations": [
      {
        "timestamp": 1593490361270000,
        "value": "cs",
        "endpoint": {
          "serviceName": "zipkin-client",
          "port": 8080
        }
      },
      {
        "timestamp": 1593490361278000,
        "value": "sr",
        "endpoint": {
          "serviceName": "zipkin-service",
          "port": 9081
        }
      },
      {
        "timestamp": 1593490361289000,
        "value": "cr",
        "endpoint": {
          "serviceName": "zipkin-client",
          "port": 8080
        }
      },
      {
        "timestamp": 1593490361290000,
        "value": "ss",
        "endpoint": {
          "serviceName": "zipkin-service",
          "port": 9081
        }
      }
    ],
    "binaryAnnotations": [
      {
        "key": "http.host",
        "value": "localhost",
        "endpoint": {
          "serviceName": "zipkin-client",
          "port": 8080
        }
      },
      {
        "key": "http.method",
        "value": "GET",
        "endpoint": {
          "serviceName": "zipkin-client",
          "port": 8080
        }
      }

    ]
  }

 

三、Zipkin架构和原理


1、Zipkin 架构

跟踪器(Tracer):位于你的应用程序中,并记录发生的操作的时间和元数据,提供了相应的类库,对用户的使用来说是透明的,收集的跟踪数据称为Span;

报告器 (Reporter):将跟踪数据发送到Zipkin server的应用程序组件称为Reporter. 比如通过MQ发送,即MQ为reporter,或者跟踪数据先落地,通过logstash发送,logstash为reporter。

Transport传输:Reporter通过几种传输方式之一将追踪数据发送到Zipkin收集器(collector),有三个主要的传输方式:HTTP, Kafka和Scribe;

Zipkin server:collector收到数据后将跟踪数据进行存储(storage),由API查询存储以向UI提供数据。

Zipkin server有4个组件组成:collector,storage,search,web UI

collector:一旦跟踪数据到达Zipkin collector守护进程,它将被验证,存储和索引,以供Zipkin收集器查找;
storage:Zipkin最初数据存储在Cassandra上,因为Cassandra是可扩展的,具有灵活的模式,并在Twitter中大量使用;但是这个组件可插入,除了Cassandra之外,还支持ElasticSearch和MySQL;  存储,zipkin默认的存储方式为in-memory,即不会进行持久化操作。如果想进行收集数据的持久化,可以存储数据在Cassandra,因为Cassandra是可扩展的,有一个灵活的模式,并且在Twitter中被大量使用,我们使这个组件可插入。除了Cassandra,我们原生支持ElasticSearch和MySQL。其他后端可能作为第三方扩展提供。
search:一旦数据被存储和索引,我们需要一种方法来提取它。查询守护进程提供了一个简单的JSON API来查找和检索跟踪,主要给Web UI使用;
web UI:创建了一个GUI,为查看痕迹提供了一个很好的界面;Web UI提供了一种基于服务,时间和注释查看跟踪的方法。
架构图如下:

架构设计(12) --分布式链路跟踪:Zipkin实践_第1张图片

 

2、Zipkin工作流程:

一个应用的代码发起HTTP get请求,经过Trace框架拦截,然后

1)、把当前调用链的Trace信息添加到HTTP Header里面
2)、记录当前调用的时间戳
3)、发送HTTP请求,把trace相关的header信息携带上
4)、调用结束之后,记录当前调用耗时
5)、然后把上面流程产生的 信息汇集成一个span,把这个span信息上传到zipkin的Collector模块

该信息来源:https://www.jianshu.com/p/d75226c8f943

┌─────────────┐ ┌───────────────────────┐  ┌─────────────┐  ┌──────────────────┐
│ User Code   │ │ Trace Instrumentation │  │ Http Client │  │ Zipkin Collector │
└─────────────┘ └───────────────────────┘  └─────────────┘  └──────────────────┘
       │                 │                         │                 │
           ┌─────────┐
       │ ──┤GET /foo ├─▶ │ ────┐                   │                 │
           └─────────┘         │ record tags
       │                 │ ◀───┘                   │                 │
                           ────┐
       │                 │     │ add trace headers │                 │
                           ◀───┘
       │                 │ ────┐                   │                 │
                               │ record timestamp
       │                 │ ◀───┘                   │                 │
                             ┌─────────────────┐
       │                 │ ──┤GET /foo         ├─▶ │                 │
                             │X-B3-TraceId: aa │     ────┐
       │                 │   │X-B3-SpanId: 6b  │   │     │           │
                             └─────────────────┘         │ invoke
       │                 │                         │     │ request   │
                                                         │
       │                 │                         │     │           │
                                 ┌────────┐          ◀───┘
       │                 │ ◀─────┤200 OK  ├─────── │                 │
                           ────┐ └────────┘
       │                 │     │ record duration   │                 │
            ┌────────┐     ◀───┘
       │ ◀──┤200 OK  ├── │                         │                 │
            └────────┘       ┌────────────────────────────────┐
       │                 │ ──┤ asynchronously report span     ├────▶ │
                             │                                │
                             │{                               │
                             │  "traceId": "aa",              │
                             │  "id": "6b",                   │
                             │  "name": "get",                │
                             │  "timestamp": 1483945573944000,│
                             │  "duration": 386000,           │
                             │  "annotations": [              │
                             │--snip--                        │
                             └────────────────────────────────┘

四、JAVA相关采集插件。


1、brave则是zipkin官方出品的Java语言的链路数据采集插件:官方提供的brave插件列表非常多,基本上涵盖了日常用到的链路:http、rpc、db等。

2、spring cloud提供了spring-cloud-sleuth-zipkin来方便集成zipkin实现zipkin client追踪器,该jar包可以通过spring-cloud-starter-zipkin依赖来引入。Spring Cloud Sleuth是采集应用Span、Trace等信息,通过HTTP Request,向Zipkin Server发送采集信息等全部自动完成。

 

 

五、zipkin结合spring-cloud-sleuth使用


我们新建三个项目。一个zipkinServer。另外两个是普通的业务应用,分别叫service和client。

zipkinServer功能SHI 收集调用数据,并展示;

service对外暴露接口;

client : 调用service接口;

1、zipkin server应用

Zipkin的使用比较简单,官网有说明几种方式: 
1、容器 :Docker Zipkin项目能够建立docker镜像,提供脚本和docker-compose.yml来启动预构建的图像。最快的开始是直接运行最新镜像:

docker run -d -p 9411:9411 openzipkin/zipkin

2、下载jar :最快的方法是下载一个zipkinserver.xx.jar并运行,详情参看

3、使用源码代码运行

1)、新建Spring Boot项目,工程取名为zipkin-server,在其pom引入依赖:

spring boot 版本:


    org.springframework.boot
    spring-boot-starter-parent
    1.4.3.RELEASE
     

spirng cloud 版本:


    
        
            org.springframework.cloud
            spring-cloud-dependencies
            Camden.SR4
            pom
            import
        
    

我们使用spirng cloud 版本:



    4.0.0
   
    
        
        
         
     
	
    com.demo.zipkin
    demo-zipkin-server
    1.0-SNAPSHOT
    jar
 
    demo-zipkin-server
    zipkin-server project for Spring Boot


    
        
        UTF-8
        1.8
        2.7
    

    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                Camden.SR4
                pom
                import
            
        
    

    
        
        
            io.zipkin.java
            zipkin-server
        
 
        
            io.zipkin.java
            zipkin-autoconfigure-ui
        

    
 

 
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

2)、在其程序入口类, 加上注解@EnableZipkinServer,开启ZipkinServer的功能:

package com.demo.zipkin.web;

/**
 * Created by huangguisu on 2019/10/28.
 */

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import zipkin.server.EnableZipkinServer;

@SpringBootApplication
@EnableZipkinServer
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class ZipkinServerApplication {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ZipkinServerApplication.class, args);

    }

}

3)、在配置文件application.yml指定,配置Zipkin服务端口、名称等:

    server.port=9411
    spring.application.name=demo-zipkin-server

如果启动报错:

Exception in thread "main" java.lang.AbstractMethodError: org.springframework.boot.context.config.ConfigFileApplicationListener.supportsSourceType(Ljava/lang/Class;)Z

架构设计(12) --分布式链路跟踪:Zipkin实践_第2张图片

去掉工程里面的pom.xml文parent依赖spring-boot-starter-parent,因为已经使用dependencyManagement配置,


        
            
                org.springframework.cloud
                spring-cloud-dependencies
                Camden.SR4
                pom
                import
            
        

如果再添加上parent依赖的话就会造成spring boot版本冲突,产生错误。

4)访问http://localhost:9411/:

架构设计(12) --分布式链路跟踪:Zipkin实践_第3张图片

2、zipkin clint和service

1)、增加依赖


    org.springframework.cloud
    spring-cloud-starter-zipkin

2)、client的application.properties设置zipkin server

server.port=8080
server.address=0.0.0.0
spring.application.name=zipkin-client
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1

3)client启动代码:

package com.demo.zipkin.web;

/**
 * Created by huangguisu on 2019/10/28.
 */

import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.web.client.RestTemplate;
import org.springframework.context.annotation.Bean;

@RestController
@SpringBootApplication
public class ClientApplication {

//    @Autowired
//    RestTemplate restTemplate;
    @Bean
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
    @GetMapping("/hello")
    public String hello(){

        return this.restTemplate().getForEntity("http://localhost:9081/hello",String.class).getBody();
    }

    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }
}

4)service的application.properties设置zipkin server

server.port=9081
server.address=0.0.0.0
spring.application.name=zipkin-client
spring.zipkin.base-url=http://localhost:9411
spring.sleuth.sampler.percentage=1

5)service启动代码:

package com.demo.zipkin.web;

/**
 * Created by huangguisu on 2019/10/28.
 */

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
@SpringBootApplication
@RestController
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class SerivceApplication {

    @GetMapping("/hello")
    public String hello(){
        return "Hello World";
    }
    public static void main(String[] args) throws Exception {
        SpringApplication.run(SerivceApplication.class, args);

    }

}

然后我们启动client和service后,访问:http://localhost:8080/hello

3、查看依赖:

可以看到如下依赖关系:

架构设计(12) --分布式链路跟踪:Zipkin实践_第4张图片

 

spring-cloud-sleuth收集信息是有一定的比率的,默认的采样率是0.1.可能在刚刚启动时候,看不到数据。

配置此值的方式在配置文件中增加spring.sleuth.sampler.percentage参数配置(如果不配置默认0.1),如果我们调大此值为1,可以看到信息收集就更及时。

如果spring.sleuth.sampler.percentage是1,由于采样频率过高,如果一次请求的链路有5个,那就需要5次http请求zipkin server,这种方式追踪服务调用链路会给我们业务程序性能带来一定的影响。

#sleuth采样率,默认为0.1,值越大收集越及时,但性能影响也越大

spring.sleuth.sampler.percentage=1


span细节指标展示:

架构设计(12) --分布式链路跟踪:Zipkin实践_第5张图片

 

使用默认zipkin方式的问题:
问题1:zipkin客户端向zipkin-server程序发送数据使用的是http的方式通信,每次发送的时候涉及到连接和发送过程。
问题2:当我们的zipkin-server程序关闭或者重启过程中,因为客户端收集信息的发送采用http的方式会被丢失。

针对以上两个问题,可以改进的办法是:
1、通信采用socket或者其他效率更高的通信方式。
2、客户端数据的发送尽量减少业务线程的时间消耗,采用异步等方式发送收集信息。
3、客户端与zipkin-server之间增加缓存类的中间件,例如redis、MQ等,在zipkin-server程序挂掉或重启过程中,客户端依旧可以正常的发送自己收集的信息。

相信采用以上三种方式会很大的提高我们的效率和可靠性。其实spring-cloud已经为我们提供采用MQ或redis等其他的采用socket方式通信,利用消息中间件或数据库缓存的实现方式。

4、spring-cloud-sleuth配置说明

 

这些配置跟版本有关,仅作参考:

spring.zipkin.base-url: http://192.168.84.146:9411/ #指定zipkin的服务器
#默认的service name是读取spring.application.name的值.
spring.zipkin.service.name=service1

#支持通过服务发现定位主机名     
spring.zipkin.locator.discovery.enabled: true  

#支持压缩的能力。默认是false。开启压缩,这样在发送给zipkin server之前会先把数据进行压缩
spring.zipkin.compression.enabled=true

#spring2.0以上 0.1-1.0 1=100%即集服务的全部追踪数据
spring.zipkin.sleuth.sampler.probability: 1.0  

#spring2.0以下 0.1-1.0    1=100%即采集服务的全部追踪数据  
spring.zipkin.sleuth.percentage: 1  

spring.zipkin.sleuth.stream.enabled: true  

#Spring Cloud Sleuth本身就整合了Spring Integration。它发布/订阅事件都是会创建span。可以设置spring.sleuth.integration.enabled=false来禁用这个机制
spring.zipkin.sleuth.stream.integration.enabled: false   

#如果不需要跟踪某些@Scheduled,可以在spring.sleuth.scheduled.skipPattern设置一些正则表达式来过滤一些class。  
spring.zipkin.sleuth.scheduled.skip-pattern: "^(org.*HystrixStreamTask|com.*ServiceAuthUtil*|com.*DBAuthClientService)$"    
        
#web开启sleuth功能          
spring.zipkin.sleuth.web.client.enabled: true

#uri在这个里面配置了,那么就不会上报到zipkin, 每个uri中间 |分割  
spring.zipkin.sleuth.web.client.skip-pattern: "/hystrix.stream" 

六、dubbo使用brave


1、添加brave依赖



            io.zipkin.brave
            brave-instrumentation-dubbo-rpc

2、设置brave dubbo filter

brave dubbo filter添加到dubbo的filter链中:

  • 方法1:在application.properties文件中添加两行配置:
dubbo.consumer.filter=tracing
dubbo.provider.filter=tracing
  • 方法2:在dubbo xml配置文件中添加配置


 

7、brave相关依赖包


使用到的brave依赖


        
            io.zipkin.brave
            brave
        

        
        
            io.zipkin.reporter2
            zipkin-sender-okhttp3
        


        
            io.zipkin.reporter2
            zipkin-sender-kafka
        


        
        
        
            io.zipkin.brave
            brave-context-slf4j
        

        
        
            io.zipkin.brave
            brave-spring-beans
        


        
        
        
            io.zipkin.brave
            brave-instrumentation-spring-webmvc
        

        
        
        
            io.zipkin.brave
            brave-instrumentation-httpclient
        

        
        
            io.zipkin.brave
            brave-instrumentation-dubbo-rpc
        

        
        
            io.zipkin.brave
            brave-instrumentation-mysql8
        

 

 

你可能感兴趣的:(服务治理,链路跟踪)