图灵学院-微服务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使用较为简单, 集成很方便。 但是功能较简单
posted on 2019-08-04 16:02 luzhouxiaoshuai 阅读(803) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2017-08-04 尚学堂 217 java中的字节码操作2
2017-08-04 尚学堂 216 java中的字节码操作
2017-08-04 尚学堂 215 在java中执行JavaScript代码