注:本人所有的spring-cloud系列的文章均为黑马的《Spring Cloud微服务架构开发》的个人笔记。
分布式服务追踪Spring Cloud Sleuth
学习目标:
- 了解Spring Cloud Sleuth基本概念
- 掌握Spring Cloud Sleuth整合Zipkin
- 掌握在Zipkin.链路中添加自定义数据
- 掌握在Zipkin中使用RabbitMQ 收集链路数据
- 握在MySQL 数据库中存储链路数据
集群中的服务数量增多,并且服务之间存在复杂的依赖关系,那么管理这些服务将会成为一件很棘手的事情,为解决以上问题,在微服务架构中实现分布式服务链路追踪变得尤为重要
在复杂的微服务架构系统中,几乎每个前端请求都会形成一条复杂的分布式服务调用链路,而在每一条链路中任何一个依赖服务出现错误时都有可能引发请求最后的失败。这时,对于每个请求,全链路调用的跟踪就变得尤其重要。通过实现对请求调用的跟踪可以帮助我们快迪发现错误根源以及监控分析每条请求链路上的性能瓶颈等。
Spring Cloud提供了sleuth框架作为解决方案,sleuth可以在整个分布式系统中跟踪一个用户请求的过程(包括数据采集,数据传输,数据存储,数据分析,数据可视化等),捕获这些跟踪的数据,并构建微服务的整个调用链的视图,为服务跟踪、解决问题提供便利。
Spring Cloud Sleuth框架具有以下4个特点:
1.提供链路追踪。通过Sleuth可以很清楚看出一个请求经过了哪些服务,方便理清服务间的调用关系
2.性能分析。通过Sleuth可以很方便看出每个请求的耗时,分析出哪些服务调用比较耗时,当服务调用的耗时随着请求量的增大而增大时,也可以对服务的扩容提供一定的提醒作用。
3.数据分析。对于一些被频繁调用的服务,可以针对业务做一些优化措施。
4.可视化。对于程序未捕获的异常,可以在Zipkin界面上看到。
2.Spring Cloud Sleuth 整合Zipkin
将微服务产生的日志交给Zipkin去分析,Zipkin是一个分布式追踪系统,主要用于收集、管理微服务产生的数据。Zipkin.的设计是基于Google Dapper实现的。在实际应用时,我们需要让各个微服务向Zipkin_服务器报告过程数据。
Spring Cloud Sleuth整合Zipkin的实现过程并不复杂,我们只需要加入依赖,以及做简单配置,就可以实现Zipkin“写入”数据。在Zipkin.得到“写入”的数据后,提供了数据查询、分析的功能,这些图形化的功能,可以让我们对微服务的调用过程、处理时间、依赖关系等数据一目了然。
案例:
2.1 搭建Eureka Server
使用以往的即可
2.2 创建Zipkin服务端zipkin-server
用于进行数据的采集存储、数据的分析和数据的展示等
pom.xml文件
添加Eurea-client、Test、Zipkin 和 Zipkin 图形化界面的依赖
<dependency>
<groupId>io.zipkin.java</ groupId>
<artifactId>zipkin-server</ artifactId>
<version>2.11.8</ version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupld>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.11.8</version>
</dependency>
application.xml
server:
port: 9411
spring:
application:
name: zipkin-server
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/ #注册中心地址
management:
metrics:
web:
server:
auto-time-requests: false #关闭自动收集web请求
在启动类添加注解
@EnableEurekaClient
@EnableZipkinServer
分析图:
3.创建网关工程gateway-service引入zuul
pom.xml
引入zuul、test、eureka-client、zipkin
<dependency>
<groupId>org.springframework.cloud</ groupId>
<artifactId>spring-cloud-starter-netflix-zuul</ artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupld>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
application.yml
server:
port: 9410
spring:
application:
name: gateway-service
zipkin:
base-url: http://localhost:9411/
sleuth:
sampler:
probability: 1.0 #值介于0到1之间,1则表示全部采集
eureka:
client:
service-url:
defaultZone: http://localhost:7000/eureka/
management:
metrics:
web:
server:
auto-time-requests: false #关闭自动收集web请求
zuul: #配置Zuul设置
routes:
eureka-provider:
path: /eureka-consumer/** #Zuul路由的前缀
host: #超时设置
socket-timeout-millis: 60000
connect-timeout-millis: 10000
在启动类添加
@EnableEurekaClient
@EnableZuulProxy
4.创建eureka-provider服务提供者
pom.xml引入 web 、eureka-client、test、zipkin
application.yml
server:
port: 7010
spring:
application:#指定应用名称
name: eureka-provider
zipkin:
base-url: http://localhost:9411/
sleuth:
sampler:
probability: 1.0
eureka:
client:
service-ur1:
defaultZone: http://1ocalhost:7000/eureka
instance:
hostname: localhost
在启动类添加:
@SpringBootAppliation
@EnbleEurekaClient
@RestController
public class EurekaProviderApplication{
public static void main(String[] args){
SpringApplication.run(EurekaProviderApplication.class,args);
}
@GetMapping("hi")
public String hi(){
return "你好,已经访问到了eureka-provider功臣中的hi接口";
}
}
5.创建eureka-consumer服务消费者
pom.xml引入 web 、eureka-client、test、zipkin
application.yml
server:
port: 7011
spring:
application:#指定应用名称
name: eureka-consumer
zipkin:
base-url: http://localhost:9411/
sleuth:
sampler:
probability: 1.0
eureka:
client:
service-ur1:
defaultZone: http://1ocalhost:7000/eureka
instance:
hostname: localhost
在启动类添加:@EnableEurekaClient
在启动类注入restTemplate 以及对网关暴露的接口
@SpringBootApplication
@nableEurekaClient
@RestController
public class EurekaConsumerApplication{
public static void main (String[] args){
SpringApplication.run(EurekaConsumerApplication.class,args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@GetMapping("/hi")
public String hi({
return restTemplate().getForObject( url: "http://eureka-provider/hi", String.class);
}
}
访问:http://localhost:9410/eureka-consumer/hi
访问: http://localhost:9411/zipkin
依赖
5.在Zipkin链路中添加自定义数据
在实际开发中,Zipkin默认展示的链路数据有时无法满足我们想要获取的数据,比如,无法在Zipkin的UI 界面中看到请求发送人的名称,这时,可以使用Zipkin添加自定义链路数据,将请求发送人的名称添加在链路数据中。在本案例中,我们在gateway-serviceI网关服务中添加Zipkin,自定义链路数据实现在Zipkin的UI界面中看到请求发送人的名称,具体步骤如下所示。
1.在gateway-service项目中创建Filter类,filter.LoggerFilter
package com.li.gatewayzuul.filter;
import brave.Tracer;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
@Component
public class LoggerFilter extends ZuulFilter {
@Autowired
Tracer tracer;
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
}
@Override
public int filterOrder() {
return 1; //值越小 越优先
}
@Override
public boolean shouldFilter() {
return true; //开启过滤器
}
@Override
public Object run() throws ZuulException {
System.out.println(tracer.currentSpan().tag("name","czbk"));
return null;
}
}
重启gateway-zuul
6.使用RibbitMQ实现链路数据收集
通过Spring Cloud S1euth整合Zipkin,已经实现了通过HTTP方式收集链路跟踪信息,除了使用HTTP 方式之外,还可以使用 RabbitMQ消息中间件异步收集链路跟踪信息。本节讲解如何通过RabbitMQ收集链路信息。
现在网络上实现微服务分布式链路追踪的资料,所使用的Spring Cloud绝大多数都是比较老的版本,由于在Spring Boot2.0 以上的版本中,官方不再推荐使用自建ZipkinServer的方式进行服务链路追踪,而是直接提供了编译好的 jar 包供开发者使用。接下来我们使用官方准备好的Zipkin Server 的 jar包讲解如何通过RabbitMQ收集链路数据。
具体步骤如下:
1、下载zipkin. jar
从Zipkin官方网站 https://zipkin.io/pages/quickstart.html下载Zipkin Server的jar包,这里我们使用的是2.17.1版本。
2、启动zipkin. jar
(1)保证 RabbitMQ启动的情况下,启动zipkin. jar。打开cmd命令行窗口,进入zipkin. jar包所在的目录下,输入下列命令。
java -jar zipkin-server-2.17.1-exec.jar
--zipkin.collector.rabbitmq.addresses=localhost
执行上述命令后,如果看到命令行中出现“Started ZipkinServer in 3.212 seconds(JVM running for 4.165)”字样时,表示命令执行成功
3.访问localh:9411
说明启动成功
4.访问localh:15672
由此看到zipkin与mq建立了联系
在eureka-consumer、eureka-provider、gateway-service添加mq相关依赖
<dependency>
<groupId>org.springframework. boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<dependency>
重启
重新访问:http://localhost:9410/eureka-consumer/hi
刷新:localhost:9411
7.在MYSQL数据库中存储链路数据
使用Spring Cloud Sleuth整合Zipkin实现了服务链路的追踪,但遗憾的是链路数据存储在内存中,无法做到持久化。Zipkin.的持久化可以结合Elasticsearch、 Cassandra或 MySQL实现。
步骤:
(1)在 zipkin-server的 pom文件中加入Zipkin.的MySQL.存储依赖zipkin-storage-mysql、MySQL的连接器依赖mysal.-connector-java、JDBC的起步依赖spring-boot-starter-jdbc以及 dbcp,连接池依赖commons-dbcp2等,具体代码如下所示。
<dependency>
<groupId>io.zipkin.java</groupld>
<artifactId>zipkin-autoconfigure-storage-mysql</artifactId>
<version>2.11.5</version>
</ dependency>
<dependency>
<groupId>io. zipkin. zipkin2</groupld>
<artifactId>zipkin-storage-mysql-v1</ artifactId>
<version>2.11.5</version>
</dependency>
<dependency>
<groupId>mysql</groupld>
<artifactId>mysql-connector-java</ artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.jooq</groupld>
<artifactId>jooq</ artifactId>
<version>3.11.4</version>
</dependency>
<!--dbcp致据库连接池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.3.0</version>
</dependency>
applcation.yml
server:
port: 9411
spring:
application:
name: zipkin-server
datasource:
url: jdbc:mysq1://localhost:3306/zipkin?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysq1.jdbc.Driver
initialization-mode: always
continue-on-error: true
schema: classpath:/zipkin.sql //此sql文件放在resource目录
rdbcp2:
#初始化连接:连接池启动时创建的初始化连接数量
initial-size: 50
#从连接池获取一个连接时,最大的等待时间,连接池会等待N毫秒,等待不到,则抛出异常
max-wait-mi1lis: 60000
#最大空闲连接:连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制
max-idle: 100
#通过这个池创建连接的默认自动提交状态。如果不设置,则setAutoCommit方法将不被调用
default-auto-commit: true
#通过这个池创建连接的默认只读状态
default-read-only: false
#指明在从池中租借对象时是否要进行验证有效,如果对象验证失败,则对象将从池子释放,然后我们将尝试租借另
一个
test-on-borrow: true
type: org.apache.commons.dbcp2.BasicDataSource
rabbitmq:
host: localhost
port:5672
username: guest
password: guest
main:
allow bean-definition-overriding: true
eureka:
client:
service-ur1:
defaultZone: http://localhost:7000/eureka/ #注册中心地址
management:
metrics:
web:
server:
auto-time-requests: false #本关闭自动收集web请求I
zipkin:
storage:
type: mysql
zipkin.sql
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,
`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'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
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(`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';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
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
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
先创建zipkin数据库,重启服务即可看到创表成功
重新访问http://localhost:9410/eureka-consumer/hi
刷新数据库 表
即可呈现数据