原因
传统单机系统在使用过程中,如果某个请求响应过慢或是响应出错,开发人员可以清楚知道某个请求出了问题,查看日志可以定位到具体方法。但是在分布式系统中,倘若客户端一个请求到达服务器后,由多个服务协作完成。比如:服务A调用服务B,服务B又调用服务C和服务D,服务D又调用服务E,那么想要知道是哪个服务处理时间过长或是处理异常导致这个请求响应缓慢或中断的话,就需要开发人员一个服务接一个服务的去机器上查看日志,先定位到出问题的服务,再定位出问题的具体地方。试想一下,随着系统越来越壮大,服务越来越多,一个请求对应处理的服务调用链越来越长,这种排查方式何其艰难。为了解决这种问题,便诞生了各种分布式场景中追踪问题的解决方案,zipkin就是其中之一。
概念
链路追踪就是追踪微服务的调用路径。
Sleuth与Zipkin
Sleuth
Sleuth是Spring Cloud提供的一款分布式系统跟踪解决方案,Sleuth可以自动为每一个请求添加唯一的跟踪标识,以便在整个请求链路中跟踪与识别请求。
学习Sleuth之前必须了解它的几个概念,它们是实现链路追踪的基础:
Trace:它代表一个完整的请求链路。每个Trace都有一个全局唯一的Trace ID用于表示整个请求的跟踪信息。
Span:Span是Trace的组成部分,它代表请求链路中的一个环节,一个请求可能经过多个服务,每个服务处理请求的过程就是一个Span。每个Span都有一个唯一的ID,用于标识该环节的跟踪信息。
Trace ID(跟踪ID):
Trace ID是全局唯一的标识,用于连接整个请求链路。在Sleuth中,Trace ID会被添加到请求的Header中,并在请求经过多个服务时一直传递下去。
Span ID(跨度ID):
Span ID是用于标识单个Span的唯一标识符。它会随着请求的流转在不同的服务之间传递,用于表示请求链路中的每个环节。
Parent Span ID(父跨度ID):
Parent Span ID是用于标识当前Span的上一级Span。通过这种方式,Span之间就可以建立起父子关系,形成完整的请求链路。
一条完整的链路请求(Trace):
Zipkin
Zipkin是一个开源的分布式跟踪系统,它收集并存储跨多个服务的请求链路数据,并提供支援的Web界面吗用于查看和分析这些数据。帮助开发人员快速定位和解决分布式系统中的性能问题。
zipkin的大致运行流程:
https://zipkin.io/pages/architecture.html
集成zipkin客户端服务通过几种传输(http,kafka,scribe)之一将数据发送到zipkin服务端,Collector将跟踪数据保存到Storage,之后Storage通过API向UI提供数据。
部分名词解释:
1.InstrumentedClient:使用了Zipkin客户端工具的服务调用方
2.InstrumentedServer:使用了Zipkin客户端工具的服务提供方
3.Non-InstrumentedServer:未使用Zipkin客户端工具的服务提供方
一个 span 表示一次服务调用,那么追踪器必定是被服务调用发起的动作触发,生成基本信息,同时为了追踪服务提供方对其他服务的调用情况,便需要传递本次追踪链路的traceId和本次调用的span-id。服务提供方完成服务将结果响应给调用方时,需要根据调用发起时记录的时间戳与当前时间戳计算本次服务的持续时间进行记录,至此这次调用的追踪span完成,就可以发送给zipkin服务端了。但是需要注意的是,发送span给zipkin collector不得影响此次业务结果,其发送成功与否跟业务无关,因此这里需要采用异步的方式发送,防止追踪系统发送延迟与发送失败导致用户系统的延迟与中断。下图就表示了一次http请求调用的追踪流程(基于zipkin官网提供的流程图):
可以看出服务A请求服务B时先被追踪器拦截,记录tag信息、时间戳,同时将追踪标识添加进http header中传递给服务B,在服务B响应后,记录持续时间,最终采取异步的方式发送给zipkin收集器。span从被追踪的服务传送到Zipkin收集器有三种主要的传送方式:http、Kafka以及Scribe(Facebook开源的日志收集系统)。
1、官网下载:zipkin官网,显示最新版本。
我下载的是 zipkin-server-2.23.9-exec.jar
2、进入cmd黑窗口—通过命令行,输入下面的命令启动ZipKin Server
java -jar zipkin-server-2.23.9-exec.jar
3、我们想要持久化链路信息,创建表将链路信息存储起来
CREATE DATABASE `zipkin` /*!40100 DEFAULT CHARACTER SET utf8 */;
use zipkin;
CREATE TABLE IF NOT EXISTS zipkin_spans (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL,
`id` BIGINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`remote_service_name` VARCHAR(255),
`parent_id` BIGINT,
`debug` BIT(1),
`start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`remote_service_name`) COMMENT 'for getTraces and getRemoteServiceNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';
CREATE TABLE IF NOT EXISTS zipkin_annotations (
`trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
`span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
`a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces and autocomplete values';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';
CREATE TABLE IF NOT EXISTS zipkin_dependencies (
`day` DATE NOT NULL,
`parent` VARCHAR(255) NOT NULL,
`child` VARCHAR(255) NOT NULL,
`call_count` BIGINT,
`error_count` BIGINT,
PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
重新启动项目
java -jar zipkin-server-2.23.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=123456
访问http://localhost:9411/
父pom
<?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.atguigu.springcloud</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Maven</name>
<modules>
<module>cloud-provider-payment8001</module>
<module>cloud-consumer-order80</module>
<module>cloud-eureka-server7001</module>
</modules>
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!-- 1、只是声明依赖,并不实际引入,子项目按需声明使用的依赖 -->
<!-- 2、子项目可以继承父项目的 version 和 scope -->
<!-- 3、子项目若指定了 version 和 scope,以子项目为准 -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
启动类上加上注解
配置文件
server:
port: 7001
eureka:
client:
register-with-eureka: false #表示不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cloud</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
controller
package com.example.cloudconsumerorder80.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
("consumer")
public class OrderController {
private RestTemplate restTemplate;
("/payment/zipkin")
public String paymentZipkin() {
String result = restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/", String.class);
return result;
}
}
ApplicationContextConfig
package com.example.cloudconsumerorder80.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
public class ApplicationContextConfig {
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
启动类上边加上
application.yml
server:
port: 801
spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order80</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
</project>
PaymentController
("payment")
public class PaymentController {
/**
* 链路监控
*
* @return
*/
("/zipkin")
public String paymentZipkin() {
return "hi,i`am paymentzipkin server fall back.welcome to atguigu.hahahahahhahahah";
}
}
application.yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
启动类上边加上
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cloud</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloud-provider-payment8001</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
发起请求http://localhost:801/consumer/payment/zipkin
Sleuth 和 Zipkin 是两个与分布式系统跟踪和监控相关的工具,它们通常与Spring Cloud和微服务架构一起使用。下面是对它们的总结:
Sleuth(Spring Cloud Sleuth):
Sleuth 是一个分布式系统的跟踪解决方案,特别是在微服务架构中。
它是Spring Cloud生态系统的一部分,可以与Spring Boot轻松集成。
Sleuth 的主要目标是为系统中的每个请求生成唯一的标识符,并记录请求在各个微服务之间的传递情况。
它会生成并注入跟踪标识符,使得开发人员能够在不同的微服务之间追踪请求,并了解请求的处理时间。
Sleuth 还支持将跟踪信息发送到多种后端存储和监控工具,如Zipkin。
Zipkin:
Zipkin 是一个开源的分布式跟踪系统,用于收集、存储和可视化跨多个微服务的请求跟踪数据。
它提供了一个直观的用户界面,用于查看跟踪数据,以及丰富的查询和过滤功能,以帮助发现性能问题。
Zipkin 允许开发人员查看请求的整体生命周期,并分析跨微服务的调用路径,从而更容易识别性能瓶颈和问题。
Zipkin 支持多种后端存储,包括Elasticsearch、Cassandra、MySQL等,因此可以根据需求选择合适的存储方式。
它还可以与Sleuth集成,以将Sleuth生成的跟踪数据发送到Zipkin进行集中化存储和分析。
总结来说,Sleuth 和 Zipkin 是在微服务架构中进行跟踪和监控的强大工具。Sleuth帮助在代码层面生成和传递唯一的跟踪标识符,而Zipkin则帮助将这些跟踪数据汇总、存储和可视化,以便开发人员能够更容易地分析系统性能和发现问题。它们的结合可以提供全面的分布式系统跟踪和监控解决方案,有助于维护和优化微服务应用程序的性能。