Spring Cloud Sleuth
1、使用场景
随着业务的发展,单体架构变为微服务架构,并且系统规模也变得越来越大,各微服务间的调用关系也变得越来越复杂。在分布式系统中,一个集群中有几十个微服务;微服务调用微服务,一个或多个微服务的网络环境问题、硬件问题导致服务提供失败;
- 多服务协同工作
在微服务的应用中,一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果。 - 复杂的调用链条容易出错
在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路,在每条链路中任何一个依赖服务出现延迟超时或者错误都有可能引起整个请求最后的失败。
2、 Sleuth概念
Spring Cloud Sleuth提供了分布式追踪(distributed tracing)的一个解决方案。其基本思路是在服务调用的请求和响应中加入ID,标明上下游请求的关系。利用这些信息,可以方便地分析服务调用链路和服务间的依赖关系。
微服务跟踪(sleuth)其实是一个工具,它在整个分布式系统中能跟踪一个用户请求的过程(包括数据采集,数据传输,数据存储,数据分析,数据可视化),捕获这些跟踪数据,就能构建微服务的整个调用链的视图,这是调试和监控微服务的关键工具。
SpringCloudSleuth有4个特点:
特点 | 说明 |
---|---|
提供链路追踪 | 通过sleuth可以很清楚的看出一个请求经过了哪些服务,可以方便的理清服务局的调用关系 |
性能分析 | 通过sleuth可以很方便的看出每个采集请求的耗时,分析出哪些服务调用比较耗时,当服务调用的耗时,随着请求量的增大而增大时,也可以对服务的扩容提供一定的提醒作用 |
数据分析优化链路 | 对于频繁地调用一个服务,或者并行地调用等,可以针对业务做一些优化措施 |
可视化 | 对于程序未捕获的异常,可以在zipkpin界面上看到 |
基本术语
Spring Cloud Sleuth采用的是Google的开源项目Dapper的专业术语。
- Span:基本工作单元,发送一个远程调度任务 就会产生一个Span,Span是一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的,Span还有其他数据信息,比如摘要、时间戳事件、Span的ID、以及进度ID。
- Trace:一系列Span组成的一个树状结构。请求一个微服务系统的API接口,这个API接口,需要调用多个微服务,调用每个微服务都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace。
- Annotation:用来及时记录一个事件的,一些核心注解用来定义一个请求的开始和结束 。这些注解包括以下:
cs - Client Sent:客户端发送一个请求,这个注解描述了这个Span的开始
sr - Server Received :服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络传输的时间。
ss - Server Sent (服务端发送响应):该注解表明请求处理的完成(当请求返回客户端),如果ss的时间戳减去sr时间戳,就可以得到服务器请求的时间。
cr - Client Received (客户端接收响应):此时Span的结束,如果cr的时间戳减去cs时间戳便可以得到整个请求所消耗的时间。
3、sleuth与zipkin集成
3.1 zipkin:
Spring Cloud Sleuth 可以结合 Zipkin,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。接下来的测试为方便直接采用 In-Memory 方式进行存储,生产推荐 Elasticsearch。
Zipkin 的基础架构,它主要由 4 个核心组件构成:
- Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为 Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
- Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
- RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
- Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。
需要先搭建Zipkin服务器:
1、添加依赖:
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
</dependency>
<!-- Zipkin可视化界面依赖 -->
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<scope>runtime</scope>
</dependency>
2、在启动类加上如下注解开启Zipkin功能
@EnableZipkinServer
3、配置Zipkin服务端口、名称等:
server.port=11008
spring.application.name=zipkin-server
4、启动后打开http://localhost:11008/可以看到如下图,什么内容都没有,因为还没有任何服务注册到Zipkin,一旦有服务注册到Zipkin便在Service Name下拉列表中可以看到服务名字,当有服务被调用,则可以在Span Name中看到被调用的接口名字
这里为了测试方便,我们可以将数据保存到内存中,但是生产环境还是需要将数据持久化的,原生支持了很多产品,例如ES、数据库等。
3.2 基于springcloud集成sleuth
1、pom文件引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
2、配置Zipkin服务器的地址,就可以开始测试Zipkin追踪服务了
spring:
application:
name: demo-application
sleuth:
web:
client:
enabled: true
sampler:
probability: 1.0 # 将采样比例设置为 1.0,也就是全部都需要。默认是 0.1
#Zipkin服务器地址
zipkin:
baseUrl: http://localhost:11008
3、 我们调用生产者或调用者的接口,然后刷新Zipkin服务器页面,可以看到如下结果:
4、查看日志结果:
日志的格式为:[application name, traceId, spanId, export]
[demo-application,5a43ec515cf49d23,5a43ec515cf49d23,true] 18076 --- [nio-8081-exec-7] com.sleuth.test.controller.UserInfoController : 测试sleuth
说明:
application name : 应用的名称,也就是application.properties中的spring.application.name参数配置的属性。
traceId : 为一个请求分配的ID号,用来标识一条请求链路。
spanId : 表示一个基本的工作单元,一个请求可以包含多个步骤,每个步骤都拥有自己的spanId : 一个请求包含一个TraceId,多个SpanId
export : 布尔类型。表示是否要将该信息输出到类似Zipkin这样的聚合器进行收集和展示。
5、可以看到,调用消费者(ribbon-consumer)耗时83ms,其中消费者调用生产者占了5ms,占比6%。
在测试的过程中我们会发现,有时候,程序刚刚启动后,刷新几次,并不能看到任何数据,原因就是我们的spring-cloud-sleuth收集信息是有一定的比率的,默认的采样率是0.1,配置此值的方式在配置文件中增加spring.sleuth.sampler.percentage参数配置(如果不配置默认0.1),如果我们调大此值为1,可以看到信息收集就更及时。但是当这样调整后,我们会发现我们的rest接口调用速度比0.1的情况下慢了很多,即使在0.1的采样率下,我们多次请求同一个接口,会发现对同一个请求两次耗时信息相差非常大,如果取消spring-cloud-sleuth后我们再测试,会发现并没有这种情况,可以看到这种方式追踪服务调用链路会给我们业务程序性能带来一定的影响。
#sleuth采样率,默认为0.1,值越大收集越及时,但性能影响也越大
spring.sleuth.sampler.percentage=1
3.3 数据传输处理
其实,我们仔细想想也可以总结出这种方式的几种缺陷
- 缺陷1:zipkin客户端向zipkin-server程序发送数据使用的是http的方式通信,每次发送的时候涉及到连接和发送过程。
- 缺陷2:当我们的zipkin-server程序关闭或者重启过程中,因为客户端收集信息的发送采用http的方式会被丢失。
针对以上两个明显的缺陷,改进的办法是:
1、通信采用socket或者其他效率更高的通信方式。
2、客户端数据的发送尽量减少业务线程的时间消耗,采用异步等方式发送收集信息。
3、客户端与zipkin-server之间增加缓存类的中间件,例如redis、MQ等,在zipkin-server程序挂掉或重启过程中,客户端依旧可以正常的发送自己收集的信息。
相信采用以上三种方式会很大的提高我们的效率和可靠性。其实spring-cloud已经为我们提供采用MQ或redis等其他的采用socket方式通信,利用消息中间件或数据库缓存的实现方式。
消息总线 RabbitMQ
我们可以通过环境变量让 Zipkin 从 RabbitMQ 中读取信息:
java -jar zipkin.jar --RABBIT_ADDRESSES=localhost
可配置的环境变量如下表所示:
Zipkin 的 Client 端加入以下依赖,就可以从 RabbitMQ 中获取 trace 信息:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>