Spring Cloud Sleuth是Spring Cloud的一个组件,它的主要功能是在分布式系统中提供服务链路追踪的解决方案。
一、Spring Cloud Sleuth介绍
微服务架构是一个分布式架构,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性较高,如果出现了错误和异常,很难去定位。主要体现在一个请求可能需要调用很多个服务,而内部服务的调用复杂性决定了问题难以定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题能够快速定位的目的。
目前,常见的链路追踪组件有Google的Dapper、Twitter的Zipkin,以及阿里的Eagleeye (鹰眼)等,它们都是非常优秀的链路追踪开源组件。
这里主要讲述如何在Spring Cloud Sleuth中集成Zipkin,在Spring Cloud Sleuth中集成Zipkin非常简单,只需要引入相应的依赖并做相关的配置即可。
Zipkin介绍
Zipkin是一个分布式跟踪系统:它可以帮助收集时间数据,解决在微服务架构下的延迟问题;它管理这些数据的收集和查找;Zipkin的设计是基于谷歌的Google Dapper论文。
每个应用程序向Zipkin报告定时数据,Zipkin UI呈现了一个依赖图表来展示多少跟踪请求经过了每个应用程序;如果想解决延迟问题,可以过滤或者排序所有的跟踪请求,并且可以查看每个跟踪请求占总跟踪时间的百分比。
二、基本术语
Spring Cloud Sleuth 采用了Google的开源项目Dapper的专业术语。
(1) Span: 基本工作单元,发送一个远程调度任务就会产生一个Span,Span是用一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的。 Span还包含了其他的信息,例如摘要、时间戳事件、Span的ID以及进程ID。
(2) Trace:由一系列Span组成的,呈树状结构。请求一个微服务系统的API接口,这个API接口需要调用多个微服务单元,调用每个微服务单元都会产生一个新的Span, 所有由这个请求产生的Span组成了这个Trace。
(3) Annotation:用于记录一个事件,一些核心注解用于定义一个请求的开始和结束,这些注解如下。
- cs-Client Sent: 客户端发送一个请求,这个注解描述了Span的开始。
- sr-Server Received:服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳,
便可得到网络传输的时间。 - ss-Server Sent:服务端发送响应,该注解表明请求处理的完成(当请求返回客户端) ,
用ss的时间戳减去sr时间戳,便可以得到服务器请求的时间。 - er-Client Received: 客户端接收响应, 此时Span结束,如果er的时间戳减去cs时间
戳,便可以得到整个请求所消耗的时间。
三、代码实现
这里继续在之前的工程项目中实现,使用eureka-server Module作为服务注册中心,新建zipkin-server Module作为链路追踪服务中心,负责存储链路数据。gateway-service Module作为服务网关工程,负责请求的转发,同时它也作为链路追踪客户端,负责产生链路数据,并上传给 zipkin-server。 eureka-producer Module是一个服务提供者,对外暴露API接口,同时它也作为链路追踪客户端,负责产生链路数据。
3.1 构建Zipkin-Server
新建一个 Module工程,取名为 zipkin-server,工程的pom文件继承了主Maven工程的pom文件,并引入Eureka的起步依赖spring-cloud-starter-eureka、Zipkin Server的依赖zipkin-server,以及Zipkin Server的UI界面依赖zipkin-autoconfigure-ui。后两个依赖提供了Zipkin的 功能和Zipkin界面展示的功能 ,代码如下 :
<parent>
<groupId>com.hand</groupId>
<artifactId>macro-service</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>
</dependencies>
在程序的启动类ZipkinServiceApplication加上@EnableZipkinServer 注解,开启ZipkinServer 的功能,加上@EnableEurekaClient注解, 启动EurekaClient,代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class ZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinServerApplication.class, args);
}
}
在程序的配置文件application.yml文件做程序的相关配置,指定程序名为zipkin-server, 端口号为9411, 服务注册地址为http://localhost:8671/eureka/,代码如下:
spring:
application:
name: zipkin-server
eureka:
client:
service-url:
defaultZone: http://localhost:8671/eureka/
server:
port: 9411
3.2 构建Eureka-Producer
在主Maven工程下建一个Module工程 ,取名为eureka-producer,作为服务提供者,对外暴露API接口。eureka-producer工程的pom文件继承了主Maven工程的pom文件,并引入了Eureka的起步依赖spring-cloud-starter-eureka、Web起步依赖 spring-boot-starter-web,以及 Zipkin 的起步依赖 spring-cloud-starter-zipkin,代码如下:
<parent>
<groupId>com.hand</groupId>
<artifactId>macro-service</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
在程序的配置文applicatiom.yml中,指定程序名为eureka-producer,端口号为8672,服务
注册地址http://localhost:8671/eureka/, Zipkin Server地址为 http://localhost:9411。spring.sleuth.sampler.percentage为 1.0,即以100%的概率将链路的数据上传给Zipkin Server,在默认情况下,该值为 0.1。配置文件代码如下:
client:
service-url:
defaultZone: http://localhost:8671/eureka/
spring:
application:
name: eureka-producer
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
percentage: 1.0
server:
port: 8672
在 UserController类建一个“/hi”的 API接口,对外提供服务, 代码如下:
@RestController
public class UserController {
@GetMapping("/hi")
public String SayHi(){
return "hi, i'm ben";
}
}
最后作为Eureka Client, 需要在程序的启动类EurekaProducerApplication加上@EnableEurekaClient注解,开启Eureka Client的功能 。
3.3 构建Gateway-Service
新建一个名为gateway-service的工程,这个工程作为服务网关,将请求转发到eureka-producer。 作为Zipkin客户端 , 需要将链路数据上传给Zipkin Server,同时它也作为Eureka Client。在工程的pom文件中除了需要继承主Maven工程的pom文件,还需引入如下的依赖,代码如下:
<parent>
<groupId>com.hand</groupId>
<artifactId>macro-service</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
在工程的配置文件application.yml中,配置程序名为gateway-service,端口号为5000,服务注 册地址为http://localhost:8671/eureka/, Zipkin Server地址为http://localhost:9411 ,以 “/user-api/**"开头的Url请求转发到服务名为eureka-producer的服务,配置代码如下:
spring:
application:
name: gateway-service
sleuth:
sampler:
percentage: 1.0
zipkin:
base-url: http://localhost:9411
server:
port: 5000
eureka:
client:
service-url:
defaultZone: http://localhost:8671/eureka/
zuul:
routes:
user-api:
path: "/user-api/**"
serviceId: eureka-producer
在程序的启动类 GatewayServiceApplication 上加@EnableEurekaClient 注解 , Client的功能,加上@EnableZuulProxy注解, 开启 Zuul代理功能,代码如下:
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
3.4 项目演示
完整的项目搭建完毕,依次启动 eureka-server、zipkin-server、eureka-producer和gateway-service。在浏览器上访问http://localhost:5000/user-api/hi, 浏览器显示如下:
访问http://localhost:9411,即访问Zipkin的展示界面, 界面显示下图所示。
这个界面用于展示Zipkin Server 收集的链路数据,可以根据服务名、开始时间、结束时间、 请求消耗的时间等条件来查找。 单击“FindTraces”按钮,界面如下图所示,从图中可知请 求的调用情况,例如请求的调用时间、消耗时间,以及请求调用的链路情况。
单击“ Dependences”按钮,可以查看服务的依赖关系。在本案例中,gateway-service 将
请求转发到了eureka-producer,这两个服务的依赖关系如下图所示。
四、在链路数据中添加自定义数据
我们除了可以通过zipkin获取链路数据外,还可以项链路数据中添加自定义数据。本案例在gateway-service服务中实现 。在gateway-service工程里新建一个 ZuulFilter过滤器,它的类型为 post类型, order为900,开启拦截。在过滤器的拦截逻辑方法里 , 通过Tracer的addTag 方法加上自定义的数据,在这里我加上了链路的操作人。另外也可以在这个过滤器中获取当前链路的traceld信息, traceld作为链路数据的唯一标识,可以存储在log日志中,方便后续查找,这里只是将traceld信息简单地打印在控制台上。 代码如下:
@Component
public class LoggerFilter extends ZuulFilter{
public static String PRE_TYPE = "pre";
@Autowired
Tracer tracer;
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 900;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
tracer.addTag("operator", "ben");
System.out.println("traceId: " + tracer.getCurrentSpan().getTraceId());
return null;
}
}
重新部署项目,访问http://localhost:5000/user-api/hi,然后访问http://localhost:9411,可以看到操作人已经被添加到链路数据中了。如下图:
后台也成功输出traceId
五、使用RabbitMQ传输链路数据
从上面的程序可知,最终gateway-service收集的数据是通过Http方式上传给zipkin-server的。但是在一些情况下微服务可能和Zipkin服务端网络不通(Zipkin服务端宕机),使用Http通信收集方式将无法工作。而使用消息组件完成微服务和Zipkin服务端的通信,既实现了微服务和Zipkin服务端的解耦,微服务不需要知道Zipkin服务端的网络地址,同时也避免因Zipkin服务器宕机而使收集数据的工作无法进行的问题。由于在Spring Cloud Sleuth中支持消息组件来传输链路数据,本节使用RabbitMQ来传输链路数据。
首先改造zipkin-server工程 , 在其pom文件中将zipkin-server的依赖去掉,加上spring-cloud-sleuth-zipkin-stream和 spring-cloud-starter-stream-rabbit 的依赖,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
在工程的配置文件application.yml加上RabbitMQ的配置,包括host、端口、用户名、密
码,代码如下:
spring:
rabbitmq:
host : localhost
port: 5672
username : guest
password : guest
在程序的启动类ZipkinServerApplication中加上@EnableZipkinStreamServer注解,开启 ZipkinStreamServer,代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableZipkinStreamServer
public class ZipkinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipkinServerApplication.class, args);
}
}
下面改造Zipkin Client (包括 gateway-service工程和eureka-producer工程),在他们的pom文件中将 spring-cloud-starter-zipkin依赖改为 spring-cloud-sleuth-zipkin-stream和spring-cloud-starter-stream-rabbit,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
同时在配置文件 applicayion.yml加上RabbitMQ的配置,同zipkin-server工程。这样,就将链路的上传数据得方式从Http改为用消息组件RabbitMQ。
六、ElasticSearch中存储链路数据
由于Zipkin Server将数据存储在内存中, 一旦程序重启,之前的链路数据全部丢失,因此需要将链路数据持久化存储。 Zipkin支持将链路数据存储在MySQL、Elasticsearch 和Cassandra数据库中。这里使用Elasticsearch存储,在并发高、大数据的情况下,使用Elasticsearch存储链路数据有明显优势,并且支持分布式文件实时存储。需要安装ElasticSearch和Kibana (下一节中使用),下载地址为https://www.elastic.co/products/elasticsearch。安装完成后启动, 其中ElasticSearch的默认端口号为9200, Kibana的默认端口号为5601。
这里直接在zipkin-server的基础上进行改造。首先在pom文件中加上zipkin的依赖和zipkin-autoconfigure-storage-elasticsearch-http的依赖,代码如下:
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>1.28.0</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-storage-elasticsearch-http</artifactId>
<version>1.28.0</version>
</dependency>
在程序的配置文件application.yml 中加上Zipkin的配置,配置了zipkin的存储类型(type) elasticsearch,使用的存储组件( StorageComponent ) 为elasticsearch,然后需要配置为elasticsearch,包括hosts (可以配置多个,用“” 隔开) 和index等。具体配置代码如下:
zipkin:
storage:
type: elasticsearch
StorageComponent: elasticsearch
elasticsearch:
cluster: elasticsearch
max-requests: 30
index: zipkin
index-shards: 3
index-replicas: 1
hosts: localhost:9200
只需要这些配置, Zipkin Server的链路数据就存储在 ElasticSearch中了。
七、用Kibana展示链路数据
上一节讲述了如何将链路数据存储在ElasticSearch中,ElasticSearch可以和Kibana结合,将链路数据展示在Kibana上。安装完成Kibana后启动,Kibana默认会向本地端口为9200的ElasticSearch读取数据。Kibana默认的端口为5601,访问Kibana的主页http://localhost:5601,其界面如下图所示。
在上图的界面中, 单击 “Management”按钮,然后单击 “CreateIndexPattern”, 添加一个 index。这里将在上节ElasticSearch中写入链路数据的index配 置为“zipkin”,那么在界面填写为“zipkin-*”,完成索引创建。创建完成index后,单击“Discover”,就可以在界面上展示链路数据了,展示界面如下图所示。
点击数据详情,可以看到整个链路数据的详细数据,还可以看到自定义添加的链路数据。如下图所示。
总结:这一章节主要学习了Sleuth的概念和使用,与zuul结合进行网关路由和服务转发以实现服务链路,和zipkin结合进行链路数据保存和处理,与ElasticSearch和kibana结合进行链路数据的持久化处理和可视化操作。
github源码地址