图灵学院-微服务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添加事件信息就是serversr日志信息 - 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 idd30318920261c306,并且該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的信息,就不會再創建一個新的spanFound 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使用較為簡單, 集成很方便。 但是功能較簡單
總結
以上是生活随笔為你收集整理的图灵学院-微服务11-分布式链路跟踪Sleuth详解的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 关于python程序设计流程(未完结)
- 下一篇: dango框架学习:四十二.django
