图灵学院-微服务11-分布式链路跟踪Sleuth详解

 当客户端访问到第一个service 1的时候,会生成当前链路追踪的一个全局的trance ID,在一次调用过Service1--Service2--Service3--Service4时,整个服务访问的过程中,trance id是唯一的

在service1中被方法被调用时通过span来表示的,浏览器访问service1的A方法的时候,从http头信息中提取span信息的时候,发现没有span信息,那么就会产生一个span,spanID为A,表示A方法被调用了,span中属性值为Server received,然后在A方法中通过rpc协议远程调用service2的B方法,这个时候会在serviceA中在创建一个span方法,重新生成一个新的spanid=B,对应的属性值为client send,这个时候会把调用的span信息放到请求头中传递给serviceB

在seriviceB中收到了serviceA的请求去调用serviceB的例如BB方法,从http的请求头信息中获得当前spanID的值为B不会重新创建一个新的span,直接从http头信息中获得spanB,spanB对应的属性值是ServerReceiver,在调用BB方法的内部中可能会开启线程进行一个内部调用,所以会重新创建一个新的span,spanid的值为c,接下来BB方法会通过rpc远程调用service3,会在servic2中创建一个span,spanid的值为D,然后将对于的信息通过http头传递给service3

其他依次类推

pom.xml

<?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>
  <groupId>com.tuling.cloud</groupId>
  <artifactId>microservice-simple-provider-user-trace</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- 引入spring boot的依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
  </dependencies>

  <!-- 引入spring cloud的依赖 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Camden.SR4</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 添加spring-boot的maven插件 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

 我们只需要加入sleuth的依赖,其他启动类不再做任何操作,他是通过无侵入式的方式进行探针监测的,使用sleuth只需要配置一个依赖就可以了

我们要在后台查看sleuth的日志,需要在application.yml中配置

server:
  port: 8000
spring:
  jpa:
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:                           # 指定数据源
    platform: h2                        # 指定数据源类型
    schema: classpath:schema.sql        # 指定h2数据库的建表脚本
    data: classpath:data.sql            # 指定h2数据库的数据脚本
  application:
    name: microservice-provider-user
logging:
  level:
    root: INFO
    org.springframework.cloud.sleuth: DEBUG
    # org.springframework.web.servlet.DispatcherServlet: DEBUG

 我们启动项目查看日志为

我们直接启动项目,通过浏览器访问user服务的接口,我们来看下日子

2019-08-04 14:04:06.162  INFO [microservice-provider-user,,,] 1376 --- [nio-8000-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2019-08-04 14:04:06.163  INFO [microservice-provider-user,,,] 1376 --- [nio-8000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2019-08-04 14:04:06.276  INFO [microservice-provider-user,,,] 1376 --- [nio-8000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 113 ms
2019-08-04 14:04:06.298 DEBUG [microservice-provider-user,,,] 1376 --- [nio-8000-exec-1] o.s.c.sleuth.instrument.web.TraceFilter  : Received a request to uri [/1] that should not be sampled [false]
2019-08-04 14:04:06.305 DEBUG [microservice-provider-user,d62e8fa0bd984c18,d62e8fa0bd984c18,false] 1376 --- [nio-8000-exec-1] o.s.c.sleuth.instrument.web.TraceFilter  : No parent span present - creating a new span
2019-08-04 14:04:06.360 DEBUG [microservice-provider-user,d62e8fa0bd984c18,d62e8fa0bd984c18,false] 1376 --- [nio-8000-exec-1] o.s.c.s.i.web.TraceHandlerInterceptor    : Handling span [Trace: d62e8fa0bd984c18, Span: d62e8fa0bd984c18, Parent: null, exportable:false]
2019-08-04 14:04:06.362 DEBUG [microservice-provider-user,d62e8fa0bd984c18,d62e8fa0bd984c18,false] 1376 --- [nio-8000-exec-1] o.s.c.s.i.web.TraceHandlerInterceptor    : Adding a method tag with value [findById] to a span [Trace: d62e8fa0bd984c18, Span: d62e8fa0bd984c18, Parent: null, exportable:false]
2019-08-04 14:04:06.362 DEBUG [microservice-provider-user,d62e8fa0bd984c18,d62e8fa0bd984c18,false] 1376 --- [nio-8000-exec-1] o.s.c.s.i.web.TraceHandlerInterceptor    : Adding a class tag with value [UserController] to a span [Trace: d62e8fa0bd984c18, Span: d62e8fa0bd984c18, Parent: null, exportable:false]
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.balance as balance3_0_0_, user0_.name as name4_0_0_, user0_.username as username5_0_0_ from user user0_ where user0_.id=?
2019-08-04 14:04:06.559 DEBUG [microservice-provider-user,d62e8fa0bd984c18,d62e8fa0bd984c18,false] 1376 --- [nio-8000-exec-1] o.s.c.sleuth.instrument.web.TraceFilter  : Closing the span [Trace: d62e8fa0bd984c18, Span: d62e8fa0bd984c18, Parent: null, exportable:false] since the response was successful
2019-08-04 14:04:06.757 DEBUG [microservice-provider-user,,,] 1376 --- [nio-8000-exec-2] o.s.c.sleuth.instrument.web.TraceFilter  : Received a request to uri [/favicon.ico] that should not be sampled [true]
2019-08-04 14:04:06.758 DEBUG [microservice-provider-user,3498035483fd9516,3498035483fd9516,false] 1376 --- [nio-8000-exec-2] o.s.c.sleuth.instrument.web.TraceFilter  : No parent span present - creating a new span
2019-08-04 14:04:06.891 DEBUG [microservice-provider-user,3498035483fd9516,3498035483fd9516,false] 1376 --- [nio-8000-exec-2] o.s.c.sleuth.instrument.web.TraceFilter  : Closing th
e span [Trace: 3498035483fd9516, Span: 3498035483fd9516, Parent: null, exportable:false] since the response was successful

 

通过日志我们可以看出,通过http://localhost:8000/1访问一个user的接口,产生了两个span: No parent span present - creating a new span

package com.tuling.cloud.study.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.tuling.cloud.study.entity.User;
import com.tuling.cloud.study.repository.UserRepository;

@RestController
public class UserController {
  @Autowired
  private UserRepository userRepository;

  @GetMapping("/{id}")
  public User findById(@PathVariable Long id) {
    User findOne = this.userRepository.findOne(id);
    return findOne;
  }
}

一个span是user收到了客户端的请求产生了一个span,在user的中通过数据库is.userRepository.findOne(id);在数据库中去查询对于的用户,也会创建一个span

整个流程如下

从上面的控制台输出内容中,我们看到多出了一些形如[trace1,454445a6a7d9ea44,912a7c66c17214e0,false]的日志信息,而这些元素正是实现分布式服务跟踪的重要组成部分,它们的含义分别如下所示:

第一个值:trace1,它表示应用的名称,也就是配置文件spring.application.name的值。

第二个值:454445a6a7d9ea44,它是SpringCloudSleuth生成的一个ID,称为Trace ID,它用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID。

第三个值:912a7c66c17214e0,它是SpringCloudSleuth生成的另外一个ID,称为Span ID,它表示一个基本的工作单元,比如发送一个http请求。

第四个值:false,表示是否要将该信息输出到Zipkin等服务中来收集和展示。

上面四个值中的Trace ID 和Span ID是SpringCloudSleuth实现分布式服务跟踪的核心。在一次服务请求链路的调用过程中,会保持并传递同一个Trace ID,从而将整个分布于不同微服务进程中的请求跟踪信息串联起来。例如,在一次前端请求链路中,上面trace1和trace2的Trace ID是相同的。

Zipkin简介 Zipkin是 Twitter开源的分布式跟踪系统,基于 Dapper的论文设计而来。它的主要功能是 收集系统的时序数据,从而追踪微服务架构的系统延时等问题。 Zipkin还提供了一个非常 友好的界面,来帮助分析追踪数据。官网地址:http://zipkin.io

pom.xml

<?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>
  <groupId>com.tuling.cloud</groupId>
  <artifactId>microservice-trace-zipkin-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- 引入spring boot的依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-autoconfigure-ui</artifactId>
    </dependency>
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-server</artifactId>
    </dependency>
  </dependencies>

  <!-- 引入spring cloud的依赖 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Edgware.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 添加spring-boot的maven插件 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

 application.yml

server:
  port: 9411

ZipkinServerApplication

package com.tuling.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import zipkin.server.EnableZipkinServer;

@SpringBootApplication
@EnableZipkinServer
public class ZipkinServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(ZipkinServerApplication.class, args);
  }
}

我们将zpkin server端启动起来

现在我们将zpinkin 服务端启动起来之后,我们要使用zpinkin 客户端集成到我们要监控的应用中,zpinkin 客户端将Sleuth采集到的数据上传到zpinkin server中

zpinkinServer将数据展示出来

简单讲解下图中各个查询条件的含义:
第一列表示Service Name,也就是各个微服务spring.application.name的值。第二列表
示Span的名称,all表示所有。Start time和End time,分别用于指定起始时间和截止时
间。Duration表示持续时间,即Span从创建到关闭所经历的时间。Limit表示查询几条数
据。类似于 MySQL数据库中的 limit关键词。Annotations Query,用于自定义查询条
件。

pom.xml

<?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>
  <groupId>com.tuling.cloud</groupId>
  <artifactId>microservice-simple-provider-user-trace-zipkin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- 引入spring boot的依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    </dependency>
  </dependencies>

  <!-- 引入spring cloud的依赖 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Edgware.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 添加spring-boot的maven插件 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

这里要配置sleuth用来收集span日志,zpinkin客户端将span日志上传给zpinkin 服务器,所有这里还需要配置zpinkin服务器的地址,以及采样率

application.yml

server:
  port: 8000
spring:
  jpa:
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:                           # 指定数据源
    platform: h2                        # 指定数据源类型
    schema: classpath:schema.sql        # 指定h2数据库的建表脚本
    data: classpath:data.sql            # 指定h2数据库的数据脚本
  application:
    name: microservice-provider-user
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      percentage: 0.1

ProviderUserApplication

package com.tuling.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

UserController

package com.tuling.cloud.study.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.tuling.cloud.study.entity.User;
import com.tuling.cloud.study.repository.UserRepository;

@RestController
public class UserController {
  @Autowired
  private UserRepository userRepository;

  @GetMapping("/{id}")
  public User findById(@PathVariable Long id) {
    User findOne = this.userRepository.findOne(id);
    return findOne;
  }
}
package com.tuling.cloud.study.entity;

import java.math.BigDecimal;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Column
  private String username;
  @Column
  private String name;
  @Column
  private Integer age;
  @Column
  private BigDecimal balance;

  public Long getId() {
    return this.id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return this.age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public BigDecimal getBalance() {
    return this.balance;
  }

  public void setBalance(BigDecimal balance) {
    this.balance = balance;
  }

}

 UserRepository.java

package com.tuling.cloud.study.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.tuling.cloud.study.entity.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

data.sql

insert into user (id, username, name, age, balance) values (1, 'account1', '张三', 20, 100.00);
insert into user (id, username, name, age, balance) values (2, 'account2', '李四', 28, 180.00);
insert into user (id, username, name, age, balance) values (3, 'account3', '王五', 32, 280.00);

schema.sql

drop table user if exists;
create table user (id bigint generated by default as identity, username varchar(40), name varchar(20), age int(3), balance decimal(10,2), primary key (id));

user微服务整合zpinkin已经整合完成了,接下来我们订单服务也要整合zpinkin,整合zpkin和整合user一样类似

接下来我们在启动下我们的订单微服务

 pom.xml

<?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>
  <groupId>com.tuling.cloud</groupId>
  <artifactId>microservice-simple-consumer-order-trace-zipkin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- 引入spring boot的依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-sleuth</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-sleuth-zipkin</artifactId>
    </dependency>
  </dependencies>

  <!-- 引入spring cloud的依赖 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Edgware.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 添加spring-boot的maven插件 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

 application.yml

server:
  port: 8010
spring:
  application:
    name: microservice-consumer-order
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      percentage: 1.0

ConsumerOrderApplication

package com.tuling.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class ConsumerOrderApplication {
  @Bean
  public RestTemplate restTemplate() {
    return new RestTemplate();
  }

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

OrderController

package com.tuling.cloud.study.user.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.tuling.cloud.study.user.entity.User;

@RestController
public class OrderController {
  @Autowired
  private RestTemplate restTemplate;

  @GetMapping("/user/{id}")
  public User findById(@PathVariable Long id) {
    return this.restTemplate.getForObject("http://localhost:8000/" + id, User.class);
  }
}

这里订单服务访问用户服务的时候,没有通过注册中心,直接使用restTemplate访问用户的url就直接调用

User

package com.tuling.cloud.study.user.entity;

import java.math.BigDecimal;

public class User {
  private Long id;
  private String username;
  private String name;
  private Integer age;
  private BigDecimal balance;

  public Long getId() {
    return this.id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return this.age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public BigDecimal getBalance() {
    return this.balance;
  }

  public void setBalance(BigDecimal balance) {
    this.balance = balance;
  }
}

 我们使用zpinkin个sleuth监控调用过程的时候
1、先启动用户服务

2.再启动订单服务

3.启动zpinkin 的Server

4、在浏览器输入订单服务的url,在zpinkin中查看整个调用过程

启动这两个项目,再启动Zipkin服务,访问订单微服务:http://localhost:8010/user/1,
然后再次查看Zipkin服务:http://localhost:9411/zipkin/,能查询到微服务调用的跟踪日

链路的追踪日志可以按照右上角的进行排序,正常和失败的调用,颜色不一样

 但是上面存在一个问题,当zpinkin Server重启之后,之前监控的数据就不存在了,之前的数据就不存在了,zpinkin server默认数据是存储在内存中,可以使用elasticSearch数据库进行保存数据

只需要在之前的zpkinServer的基础上增加下面的依赖就可以了

<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-storage-elasticsearch-http</artifactId>
<version>2.3.1</version>
</dependency>

<?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>
  <groupId>com.tuling.cloud</groupId>
  <artifactId>microservice-trace-zipkin-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <!-- 引入spring boot的依赖 -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.9.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-autoconfigure-ui</artifactId>
    </dependency>
    <dependency>
      <groupId>io.zipkin.java</groupId>
      <artifactId>zipkin-server</artifactId>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/io.zipkin.java/zipkin-autoconfigure-storage-elasticsearch-aws -->
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-storage-elasticsearch-http</artifactId>
    <version>2.3.1</version>
</dependency>
    
  </dependencies>

  <!-- 引入spring cloud的依赖 -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Edgware.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <!-- 添加spring-boot的maven插件 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

 application.yml

server:
  port: 9411
zipkin: 
  storage: 
    type: elasticsearch 
    elasticsearch: 
      cluster: elasticsearch
      hosts: http://localhost:9200
      index: zipkin
      index-shards: 5
      index-replicas: 1  

 hosts: http://localhost:9200是elasticSearch数据库访问的地址

ZipkinServerApplication

package com.tuling.cloud.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import zipkin.server.EnableZipkinServer;

@SpringBootApplication
@EnableZipkinServer
public class ZipkinServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(ZipkinServerApplication.class, args);
  }
}

 当我们在浏览器输入http://localhost:8000/3

 除了使用elasticSeach数据库之后,zinpkin还可以整合mysql数据库和rabbit mq中间件

但这样每个请求都会向zipkin server发送http请求,通信效率低,造成网络延迟。

  而且所用的追踪信息都在内存中保存,重启zipkin server后信息丢失

  针对以上的问题的解决方法:

    a 采用socket或高效率的通信方式

    b 采用异步方式发送信息数据

    c 在客户端和zipkin之间增加缓存类的中间件,如redis,mq等,即时zipkin server重启过程中,客户端依然可以将数据发送成功

3 将http通信改为mq异步通信方式

zipkin

我们来看zipkin中采集到的数据的信息

 zinpkin还可以和mysql进行整合,这里不做讲解了,具体看博客:https://www.cnblogs.com/lifeone/p/9040336.html

整个过程中产生了两个span,我们选中order这个服务,单击鼠标左键,会弹出当前order对应的span的信息

点击页面上的json按钮,可以看到改span对于的json信息,上面的span页面上展示的数据就是依据json数据画出来的

[
[
  {
    "traceId": "0d479b6474f876da",
    "id": "0d479b6474f876da",
    "name": "http:/user/3/",
    "timestamp": 1564929029123000,
    "duration": 4208479,
    "annotations": [
      {
        "timestamp": 1564929029123000,
        "value": "sr",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "timestamp": 1564929033331479,
        "value": "ss",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      }
    ],
    "binaryAnnotations": [
      {
        "key": "http.host",
        "value": "localhost",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.method",
        "value": "GET",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.path",
        "value": "/user/3/",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.status_code",
        "value": "200",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.url",
        "value": "http://localhost:8010/user/3/",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "mvc.controller.class",
        "value": "OrderController",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "mvc.controller.method",
        "value": "findById",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "spring.instance_id",
        "value": "192.168.0.102:microservice-consumer-order:8010",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      }
    ]
  },
  {
    "traceId": "0d479b6474f876da",
    "id": "d30318920261c306",
    "name": "http:/3",
    "parentId": "0d479b6474f876da",
    "timestamp": 1564929029328000,
    "duration": 3875000,
    "annotations": [
      {
        "timestamp": 1564929029328000,
        "value": "cs",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "timestamp": 1564929029609000,
        "value": "sr",
        "endpoint": {
          "serviceName": "microservice-provider-user",
          "ipv4": "192.168.0.102",
          "port": 8000
        }
      },
      {
        "timestamp": 1564929033181079,
        "value": "ss",
        "endpoint": {
          "serviceName": "microservice-provider-user",
          "ipv4": "192.168.0.102",
          "port": 8000
        }
      },
      {
        "timestamp": 1564929033203000,
        "value": "cr",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      }
    ],
    "binaryAnnotations": [
      {
        "key": "http.host",
        "value": "localhost",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.method",
        "value": "GET",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.path",
        "value": "/3",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "http.url",
        "value": "http://localhost:8000/3",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "mvc.controller.class",
        "value": "UserController",
        "endpoint": {
          "serviceName": "microservice-provider-user",
          "ipv4": "192.168.0.102",
          "port": 8000
        }
      },
      {
        "key": "mvc.controller.method",
        "value": "findById",
        "endpoint": {
          "serviceName": "microservice-provider-user",
          "ipv4": "192.168.0.102",
          "port": 8000
        }
      },
      {
        "key": "spring.instance_id",
        "value": "192.168.0.102:microservice-consumer-order:8010",
        "endpoint": {
          "serviceName": "microservice-consumer-order",
          "ipv4": "192.168.0.102",
          "port": 8010
        }
      },
      {
        "key": "spring.instance_id",
        "value": "192.168.0.102:microservice-provider-user:8000",
        "endpoint": {
          "serviceName": "microservice-provider-user",
          "ipv4": "192.168.0.102",
          "port": 8000
        }
      }
    ]
  }
]


红色的json数据为第一个order订单的span节点信息,绿色部分为第二个user用户对应的span的信息
 红色的jsonjson数据中annotations属性中对应的的是改span的事件信息,对于第一个span页面上面页面上的这个信息

红色json中binaryAnnotations对应的是业务数据,对应上面页面上的key信息



我们选择user模块,点击右键会弹出第二个span的信息

 接下来我们对两个span进行详细分析

术语(Terminology)
Span:基本工作单元,例如,在一个新建的span中发送一个RPC等同于发送一个回应请求给RPC,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)
span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。
Trace:一系列spans组成的一个树状结构,例如,如果你正在跑一个分布式大数据工程,你可能需要创建一个trace。
Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束


cs- Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始

sr- Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟

ss- Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间

cr- Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间

将Span和Trace在一个系统中使用Zipkin注解的过程图形化:

每个颜色的注解表明一个span(总计7个spans,从A到G),如果在注解中有这样的信息:上面中一个存在7个span,spanid为A的span记录了调用servic1的日志信息,spanid为B的span记录servic1调用service2的日志信息
Trace Id = X
Span Id = D
Client Sent
这就表明当前span将Trace-Id设置为X,将Span-Id设置为D,同时它还表明了ClientSent事件。

 spans 的parent/child关系图形化

我们需要在user服务和订单服务中都开启sleuth的日志信息,来查看对于的日志

user端的application.yml

server:
  port: 8000
spring:
  jpa:
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:                           # 指定数据源
    platform: h2                        # 指定数据源类型
    schema: classpath:schema.sql        # 指定h2数据库的建表脚本
    data: classpath:data.sql            # 指定h2数据库的数据脚本
  application:
    name: microservice-provider-user
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      percentage: 1.0
logging:
  level:
    org.springframework.cloud.sleuth: DEBUG       

 order端的application.yml

server:
  port: 8010
spring:
  application:
    name: microservice-consumer-order
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      percentage: 1.0
logging:
  level:
    org.springframework.cloud.sleuth: DEBUG      

 我们首先抓取oder订单中的日志


o.s.c.sleuth.instrument.web.TraceFilter : Received a request to uri [/user/3/] that should not be sampled [false]
o.s.c.sleuth.instrument.web.TraceFilter : No parent span present - creating a new span
o.s.c.s.i.web.TraceHandlerInterceptor : Handling span [Trace: 0d479b6474f876da, Span: 0d479b6474f876da, Parent: null, exportable:true]
.s.c.s.i.web.TraceHandlerInterceptor : Adding a method tag with value [findById] to a span [Trace: 0d479b6474f876da, Span: 0d479b6474f876da, Parent: null, exportable:true]
o.s.c.s.i.web.TraceHandlerInterceptor : Adding a class tag with value [OrderController] to a span [Trace: 0d479b6474f876da, Span: 0d479b6474f876da, Parent: null, exportable:true]
.w.c.AbstractTraceHttpRequestInterceptor : Starting new client span [[Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]]
o.s.c.s.zipkin2.DefaultEndpointLocator : Span will contain serviceName [microservice-consumer-order]
o.s.c.sleuth.instrument.web.TraceFilter : Closing the span [Trace: 0d479b6474f876da, Span: 0d479b6474f876da, Parent: null, exportable:true] since the response was successful

 

首先通过浏览器发起http://localhost:8010/user/3/这个请求,这个请求被sleuth的TraceFilter拦截,拦截器获取请求的http头的信息,看信息中是否携带了span的信息,通过浏览器访问请求头中都没有携带

span的信息,所以TraceFilter中No parent span present - creating a new span,创建一个新的span,span携带当前的tranceID为0d479b6474f876da,对于的spanID为0d479b6474f876da,Parent为null

exportable:true为true表示当前的span信息会上传给zinpkin进行展示,

package com.tuling.cloud.study.user.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.tuling.cloud.study.user.entity.User;

@RestController
public class OrderController {
  @Autowired
  private RestTemplate restTemplate;

  @GetMapping("/user/{id}")
  public User findById(@PathVariable Long id) {
    return this.restTemplate.getForObject("http://localhost:8000/" + id, User.class);
  }
}

TraceFeignClient: 请求端注入的FeignClient,为Request的Header添加SpanID, TraceID等信息
TraceFilter: 接收端注入的定制Filter,它将解析Request中的Header,执行业务,计算耗时,最终算出一个完整的JSON格式的Span,通过队列异步发送到收集器ZipKin中
ZipKin: 日志收集器,读取JSON格式的SPAN信息,并存储与展示

同时会将当前浏览器访问的OrderController类以及findById方法添加到span中,便于zinpkin页面中key信息进行显示 Adding a method tag with value [findById],Adding a class tag with value [OrderController],

同时需要给改span添加事件信息就是server sr日志信息 - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟

接下来执行this.restTemplate.getForObject("http://localhost:8000/" + id, User.class);,整个时候会发起远程的restTemplate的调用,这个时候会重新创建一个span

Starting new client span [[Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]],会重新生成一个新的span id d30318920261c306,并且该span的Parent为之前的span: Parent: 0d479b6474f876da, exportable:true表示该span也是要被zinpkin进行展示。

接下来进行远程调用,当远程调用完毕。server1把结果返回给浏览器之前,需要将span关闭,打印下面的这两个日志

o.s.c.s.zipkin2.DefaultEndpointLocator : Span will contain serviceName [microservice-consumer-order]
o.s.c.sleuth.instrument.web.TraceFilter : Closing the span [Trace: 0d479b6474f876da, Span: 0d479b6474f876da, Parent: null, exportable:true] since the response was successful

接下来我们来看service1发起 this.restTemplate.getForObject("http://localhost:8000/" + id, User.class);远程调用,在远程调用的时候会把ID为 Span: d30318920261c306的span添加到http头信息中传递给user端,此时对于的事件日志为:cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始,我们来看user端打印的日志

 o.s.c.sleuth.instrument.web.TraceFilter  : Received a request to uri [/3] that should not be sampled [false]
o.s.c.sleuth.instrument.web.TraceFilter  : Found a parent span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true] in the request
 o.s.c.sleuth.instrument.web.TraceFilter  : Parent span is [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]
o.s.c.s.i.web.TraceHandlerInterceptor    : Handling span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]
o.s.c.s.i.web.TraceHandlerInterceptor    : Adding a method tag with value [findById] to a span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]
 o.s.c.s.i.web.TraceHandlerInterceptor    : Adding a class tag with value [UserController] to a span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.balance as balance3_0_0_, user0_.name as name4_0_0_, user0_.username as username5_0_0_ from user user0_ where user0_.id=?
 o.s.c.sleuth.instrument.web.TraceFilter  : Trying to send the parent span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true] to Zipkin
o.s.c.s.zipkin2.DefaultEndpointLocator   : Span will contain serviceName [microservice-provider-user]
 o.s.c.sleuth.instrument.web.TraceFilter  : Closing the span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true] since the response was successful

发起远程调用的时候,首先TraceFilter也拦截到当前的远程调用,查看当前的远程调用的http头信息(in the request)中是否存在span的信息,当前发现了span的信息,就不会再创建一个新的span Found a parent span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true] in the request,从请求头中获得span的信息,这个时候对于的事件日志就是:sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟。得到span信息之后接下来开始对span进行处理

package com.tuling.cloud.study.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.tuling.cloud.study.entity.User;
import com.tuling.cloud.study.repository.UserRepository;

@RestController
public class UserController {
  @Autowired
  private UserRepository userRepository;

  @GetMapping("/{id}")
  public User findById(@PathVariable Long id) {
    //User findOne = this.userRepository.findOne(id);
      //aa(id);
      try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
      new Thread(new Runnable() {
        
        @Override
        public void run() {
            // TODO Auto-generated method stub
            
        }
    }).start();
    return aa(id);
  }
  
  public User aa( Long id) {
        User findOne = this.userRepository.findOne(id);
        return findOne;
      }
}

将当前的 UserController类信息和findById方法添加到span日志信息中,打印日志

Adding a method tag with value [findById] to a span [Trace: 0d479b6474f876da, Span: d30318920261c306, Parent: 0d479b6474f876da, exportable:true]
o.s.c.s.i.web.TraceHandlerInterceptor : Adding a class tag with value

接下来开始查询数据库查询出当前的用户

接下来用户查询完成之后,需要将当前的用户信息返回给订单服务,返回给订单服务之前需要将当前的span进行关闭,此次对于的事件信息是ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间,并且需要将当前的span信息上传给zinpkin显示,将id为d30318920261c306的span进行关闭

当订单服务收到用户服务返回的信息后,此时对于的事件信息为:cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间,当订单服务吧数据返回给浏览器之后,需要关闭span id为0d479b6474f876da的span,这就是整个流程

通过:Annotation我们就可以得到整个调用链的时间

Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
  cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始
  sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
  ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
  cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间

上面绿色框图对于的就是spanid=0d479b6474f876da的信息,该span中展示了浏览器访问serivice1中的OrderController类中的findById的信息

紫色框图代表的的是spanid=d30318920261c306的信息,该span展示了service1中通过 this.restTemplate.getForObject("http://localhost:8000/" + id, User.class)远程调用user服务中UserController类

中findById方法的信息

与Zipkin整合——API接口

Zipkin不仅提供了Web UI方便用户进行跟踪信息查看与查询,同时还提供了Rest API,方便第三方系统进行集成进行跟踪信息的展示和监控,其提供的API列表如下所示:

Zipkin Server主要包括四个模块:
(1)Collector 接收或收集各应用传输的数据
(2)Storage 存储接受或收集过来的数据,当前支持Memory,MySQL,Cassandra,ElasticSearch等,默认存储在内存中。
(3)API(Query) 负责查询Storage中存储的数据,提供简单的JSON API获取数据,主要提供给web UI使用
(4)Web 提供简单的web界面

 zinpkin的缺点:只能统计zipkin只能统计接口级别的信息,不能统计应用service级别的统计信息,skywalking能够统计接口和应用级别的信息

1、只支持spring clould应用,不支持dubbo协议

3、该产品结合spring-cloud-sleuth使用较为简单, 集成很方便。 但是功能较简单

原文地址:https://www.cnblogs.com/kebibuluan/p/11298506.html