附:SpringCloud之系列教程汇总跳转地址
Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可。
Zipkin是Twitter的一个开源项目,它基于Google Dapper实现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的API接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
上图展示了Zipkin的基础架构,它主要有4个核心组件构成:
基础概念
我们先来看看Zipkin中关于跟踪信息的一些基础概念。由于Zipkin的实现借鉴了Google的Dapper,所以它们有着类似的核心术语,主要有下面几个内容:
Span
:它代表了一个基础的工作单元。我们以HTTP请求为例,一次完整的请求过程在客户端和服务端都会产生多个不同的事件状态(比如下面所说的四个核心Annotation
所标识的不同阶段),对于同一个请求来说,它们属于一个工作单元,所以同一HTTP请求过程中的四个Annotation
同属于一个Span。每一个不同的工作单元都通过一个64位的ID来唯一标识,称为Span ID。另外,在工作单元中还存储了一个用来串联其他工作单元的ID,它也通过一个64位的ID来唯一标识,称为Trace ID。在同一条请求链路中的不同工作单元都会有不同的Span ID,但是它们的Trace ID是相同的,所以通过Trace ID可以将一次请求中依赖的所有依赖请求串联起来形成请求链路。除了这两个核心的ID之外,Span中还存储了一些其他信息,比如:描述信息、事件时间戳、Annotation的键值对属性、上一级工作单元的Span ID等。
Trace
:它是由一系列具有相同Trace ID的Span串联形成的一个树状结构。在复杂的分布式系统中,每一个外部请求通常都会产生一个复杂的树状结构的Trace。
Annotation
:它用来及时地记录一个事件的存在。我们可以把Annotation理解为一个包含有时间戳的事件标签,对于一个HTTP请求来说,在Sleuth中定义了下面四个核心Annotation来标识一个请求的开始和结束:
cs
(Client Send):该Annotation用来记录客户端发起了一个请求,同时它也标识了这个HTTP请求的开始。sr
(Server Received):该Annotation用来记录服务端接收到了请求,并准备开始处理它。通过计算sr
与cs
两个Annotation的时间戳之差,我们可以得到当前HTTP请求的网络延迟。ss
(Server Send):该Annotation用来记录服务端处理完请求后准备发送请求响应信息。通过计算ss
与sr
两个Annotation的时间戳之差,我们可以得到当前服务端处理请求的时间消耗。cr
(Client Received):该Annotation用来记录客户端接收到服务端的回复,同时它也标识了这个HTTP请求的结束。通过计算cr
与cs
两个Annotation的时间戳之差,我们可以得到该HTTP请求从客户端发起开始到接收服务端响应的总时间消耗。BinaryAnnotation
:它用来对跟踪信息添加一些额外的补充说明,一般以键值对方式出现。比如:在记录HTTP请求接收后执行具体业务逻辑时,此时并没有默认的Annotation
来标识该事件状态,但是有BinaryAnnotation
信息对其进行补充。
收集机制
在理解了Zipkin的各个基本概念之后,下面我们结合前面章节中实现的例子来详细介绍和理解Spring Cloud Sleuth是如何对请求调用链路完成跟踪信息的生产、输出和后续处理的。
首先,我们来看看Sleuth在请求调用时是怎么样来记录和生成跟踪信息的。下图展示了我们在本章节中实现示例的运行全过程:客户端发送了一个HTTP请求到trace-1
,trace-1
依赖于trace-2
的服务,所以trace-1
再发送一个HTTP请求到trace-2
,待trace-2
返回响应之后,trace-1
再组织响应结果返回给客户端。
在上图的请求过程中,我们为整个调用过程标记了10个标签,它们分别代表了该请求链路运行过程中记录的几个重要事件状态,我们根据事件发生的时间顺序我们为这些标签做了从小到大的编号,1代表请求的开始、10代表请求的结束。每个标签中记录了一些我们上面提到过的核心元素:Trace ID、Span ID以及Annotation。由于这些标签都源自一个请求,所以他们的Trace ID相同,而标签1和标签10是起始和结束节点,它们的Trace ID与Span ID是相同的。
根据Span ID,我们可以发现在这些标签中一共产生了4个不同ID的Span,这4个Span分别代表了这样4个工作单元:
trace-1
和trace-1
发送请求响应的两个事件,它可以计算出了客户端请求响应过程的总延迟时间。trace-1
应用在接收到客户端请求之后调用处理方法的开始和结束两个事件,它可以计算出trace-1
应用用于处理客户端请求时,内部逻辑花费的时间延迟。trace-1
应用发送请求给trace-2
应用、trace-2
应用接收请求,trace-2
应用发送响应、trace-1
应用接收响应四个事件,它可以计算出trace-1
调用trace-2
的总体依赖时间(cr - cs),也可以计算出trace-1
到trace-2
的网络延迟(sr - cs),也可以计算出trace-2
应用用于处理客户端请求的内部逻辑花费的时间延迟(ss - sr)。trace-2
应用在接收到trace-1
的请求之后调用处理方法的开始和结束两个事件,它可以计算出trace-2
应用用于处理来自trace-1
的请求时,内部逻辑花费的时间延迟本篇我们采用mq异步通信的方式,应用客户端将跟踪信息输出到消息中间件上,同时Zipkin服务端从消息中间件上异步地消费这些跟踪信息,并存储在Elasticsearch中
在前几篇的基础上,新建ZipkinServer项目
ZipkinServer项目
pom.xml
4.0.0
com.yj
SpringCloud
0.0.1-SNAPSHOT
ZipkinServer
ZipkinServer
http://maven.apache.org
UTF-8
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-sleuth-zipkin-stream
org.springframework.cloud
spring-cloud-starter-stream-rabbit
io.zipkin.java
zipkin-autoconfigure-ui
runtime
io.zipkin.java
zipkin
1.24.0
org.springframework.cloud
spring-cloud-starter-sleuth
io.zipkin.java
zipkin-autoconfigure-storage-elasticsearch-http
1.24.0
true
${artifactId}
org.springframework.boot
spring-boot-maven-plugin
true
org.apache.maven.plugins
maven-jar-plugin
com.yj.zipkin.ZipkinServerApplication
true
lib
./
config/**
**.xml
**.properties
maven-assembly-plugin
false
src/main/build/package.xml
make-assembly
package
single
src/main/resources
ZipkinServerApplication
package com.yj.zipkin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.sleuth.zipkin.stream.EnableZipkinStreamServer;
@SpringBootApplication
@EnableZipkinStreamServer
public class ZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinServerApplication.class, args);
}
}
application.properties
server.port=8014
spring.application.name=zipkin
#表示当前程序不使用sleuth
spring.sleuth.enabled=false
#表示zipkin数据存储方式
zipkin.storage.StorageComponent=elasticsearch
zipkin.storage.type=elasticsearch
zipkin.storage.elasticsearch.cluster=elasticsearch
zipkin.storage.elasticsearch.hosts=localhost:9300
zipkin.storage.elasticsearch.max-requests=64
zipkin.storage.elasticsearch.index=zipkin
zipkin.storage.elasticsearch.index-shards=5
zipkin.storage.elasticsearch.index-replicas=1
#spring boot数据源配置
#spring.datasource.url=jdbc:mysql://localhost:3306/zipkin?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.continue-on-error=true
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
zipkin.sql(mysql存储时需要此ddl)
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
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;
ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);
我们改造前几篇的EurekaClient,Feign,Zuul项目的pom文件和application.properties文件
pom文件新增依赖
org.springframework.cloud
spring-cloud-sleuth-zipkin-stream
org.springframework.cloud
spring-cloud-starter-stream-rabbit
org.springframework.cloud
spring-cloud-starter-sleuth
application.properties文件 新增rabbitMq配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
docker启动rabbitmq,es,mysql镜像,然后执行sql,部署启动ZipkinServer,EurekaClient,Feign,Zuul项目,成功后我们随便访问几个路由地址:
比如zuul项目的http://192.168.37.147:8008/api-feign/hiFeign?name=yj&token=123
Feign项目的http://192.168.37.147:8006/hiFeign?name=yj
EurekaClient项目的http://192.168.37.147:8003/hiEureka,生成一些链路追踪数据
然后访问ZipkinServer项目http://192.168.37.147:8014/,可以看到链路追踪数据了
mysql存储时dependencies显示正常,采用es存储时dependencies不能正常显示,需另外启动一个官方依赖包,这里我用的是zipkin-dependencies-2.0.3.jar版本,启动脚本
#! /bin/bash
moduleName="zipkin-dependencies-2.0.3"
STORAGE_TYPE=elasticsearch ES_HOSTS=127.0.0.1:9200 java -jar -Dspark.testing.memory=1073741824 ./$moduleName.jar &
我们先启动此依赖包,然后再启动ZipkinServer,Zuul,EurekaClient,Feign等项目,dependencies也能正常显示了
附:SpringCloud之系列教程汇总跳转地址