Spring Cloud 之 服务链路跟踪Spring Cloud Sleuth
1、Spring Cloud Sleuth 简介
Spring Cloud Sleuth为微服务之间调用提供了一套完整的服务链路跟踪解决方案。通过Sleuth我们可以清楚了解一次完整请求经过的微服务及其微服务之间的调用关系,同时我们还可以知道每个微服务的调用时间。
Sleuth可以实现如下功能:
- 微服务耗时分析
- 链路分析优化
- 请求错误分析,基于Zipkin可以实现错误的可视化。
2、常见概念
- Span,是Sleuth中最基本的工作单元,即每个微服务被调用一次,就会产生一个新的Span。Span有起始和结束,同时记录请求到达时间和离开时间等信息,所以可以用于跟踪服务处理时间信息。
- SpanId,Span使用唯一的、长度为64位的ID作为标识,即SpanId。
- Trace,一次用户的完整请求所涉及的所有Span的集合(即经过的所有微服务),采用树形结构进行管理。
- TraceId,同一个用户请求,即为同一条链路,并赋值一个相同的TraceID,通过该标识就可以在多个微服务之间找到完整的处理链路。
- Annotation,用于记录时间信息。其中,cs(Client Sent)表示客户端发送时间,标志一个Span生命周期的开始;sr(Server Received)表示服务端接收并开始处理的时间;ss(Server Sent)表示服务端完成请求处理并发回客户端的时间;cr(Client Received)客户端接收到返回内容的时间,标志着一个Span生命周期的结束。
3、搭建Sleuth场景
使用Sleuth的方式很简单,在需要跟踪的微服务的pom文件中引入sleuth相关依赖即可,如下所示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
我们这里构建了两个微服务sleuth-span1和sleuth-span2,其中sleuth-span2服务中的/feignApi接口会调用sleuth-span1服务中的/provider接口,这样就形成了一个调用链路。当我们启动项目后,访问/feignApi接口,在两个服务的控制台就会打印如下日志:
sleuth-span2服务打印日志:
2020-11-19 21:57:12.883 INFO [sleuth-span2,6d0f9994427f5ad3,6d0f9994427f5ad3,true] 31144 --- [nio-8111-exec-8] com.qriver.cloud.consumer : 执行消费者FeignController的feignApi()方法!
sleuth-span1服务打印日志:
2020-11-19 21:57:13.457 INFO [sleuth-span1,6d0f9994427f5ad3,6fff738ea525246c,true] 10732 --- [nio-8110-exec-5] com.qriver.cloud.provider : 调用服务提供者ProviderController的provider()方法!
上述的日志中,出现了形如[sleuth-span2,6d0f9994427f5ad3,6d0f9994427f5ad3,true]的日志信息,这些信息就是Sleuth分布式服务跟踪的重要组成部分,每个元素的含义如下:
- 第一个值:sleuth-span1或sleuth-span2,它记录了应用的名称。
- 第二个值:6d0f9994427f5ad3,链路ID,即Trace ID,它用来标识一条请求链路。一条请求链路中包含一个Trace ID,多个Span ID。
- 第三个值:6d0f9994427f5ad3或6fff738ea525246c,表示Span ID,Span为一个基本的工作单元,即微服务中一次调用。
- 第四个值:true,表示是否要将该信息输出到Zipkin等服务中来收集和展示。
4、基于Zipkin的链路跟踪可视化
Zipkin致力于收集分布式系统中的时间数据,并进行跟踪。通过Zipkin可以为开发者采集一个外部请求所跨多个微服务之间的服务跟踪数据,同时以可视化的方式为开发者展现服务请求所跨越多个微服务中耗费的总时间及各个微服务所耗费的时间。可以说Zipkin是微服务架构下一个用来监控微服务效能的非常强大的工具。
Zipkin有四个组件,分别是:collector为数据采集组件;storage为数据存储组件;search为数据查询组件;UI为数据展示组件。其中存储组件支持In-Memory、MySQL、Cassandra、Elasticsearch四种存储方式。
架构图来源互联网
4.1、搭建Zipkin服务
为了方便查看请求的链路信息,我们搭建了zipkin-server服务。因为ZipKin不是Spring Cloud的子项目,而是Twitter的一个开源项目,所以在版本匹配上需要注意,很容易出现版本不匹配或者jar冲突的问题,我这里SpringBoot选用了2.1.4.RELEASE版本,而ZipKin选用了2.12.3版本。
首先,在pom文件中添加依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-server</artifactId>
<version>2.12.3</version>
<exclusions>
<exclusion>
<artifactId>log4j-slf4j-impl</artifactId>
<groupId>org.apache.logging.log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.zipkin.java</groupId>
<artifactId>zipkin-autoconfigure-ui</artifactId>
<version>2.12.3</version>
</dependency>
然后,修改application.properties配置文件。这里主要需要注意的是management.metrics.web.server.auto-time-requests配置,如果添加该配置,启动时可能会出现报错。
spring.application.name=zipkin-server
server.port=8112
# 关闭自动配置启用所有请求得检测
management.metrics.web.server.auto-time-requests=false
不添加management.metrics.web.server.auto-time-requests时出现的报错,如下:
java.lang.IllegalArgumentException: Prometheus requires that all meters with the same name have the same set of tag keys. There is already an existing meter named 'http_server_requests_seconds' containing tag keys [exception, method, outcome, status, uri]. The meter you are attempting to register has keys [method, status, uri].
at io.micrometer.prometheus.PrometheusMeterRegistry.lambda$collectorByName$9(PrometheusMeterRegistry.java:372) ~[micrometer-registry-prometheus-1.1.4.jar:1.1.4]
at java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1877) ~[na:1.8.0_121]
at io.micrometer.prometheus.PrometheusMeterRegistry.collectorByName(PrometheusMeterRegistry.java:359) ~[micrometer-registry-prometheus-1.1.4.jar:1.1.4]
at io.micrometer.prometheus.PrometheusMeterRegistry.newTimer(PrometheusMeterRegistry.java:165) ~[micrometer-registry-prometheus-1.1.4.jar:1.1.4]
at io.micrometer.core.instrument.MeterRegistry.lambda$timer$2(MeterRegistry.java:270) ~[micrometer-core-1.1.4.jar:1.1.4]
at io.micrometer.core.instrument.MeterRegistry.getOrCreateMeter(MeterRegistry.java:575) ~[micrometer-core-1.1.4.jar:1.1.4]
at io.micrometer.core.instrument.MeterRegistry.registerMeterIfNecessary(MeterRegistry.java:528) ~[micrometer-core-1.1.4.jar:1.1.4]
at io.micrometer.core.instrument.MeterRegistry.timer(MeterRegistry.java:268) ~[micrometer-core-1.1.4.jar:1.1.4]
……
再,配置启动类。主要是需要添加@EnableZipkinServer注解,启动zipkin服务功能。
@SpringBootApplication
@EnableZipkinServer
public class ZipKinServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZipKinServerApplication.class, args);
}
}
最后,启动服务,然后访问http://localhost:8112/zipkin 地址,出现如下界面,说明部署成功了。
4.2、修改应用服务sleuth-span1和sleuth-span2
在原来的两个微服务上,直接进行修改,只需要修改pom文件和application.properties即可。
首先,pom文件,需要增加如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
然后,修改application.properties,增加Zipkin服务配置,如下:
#配置Zipkin Server信息,默认配置是http://localhost:9411
spring.zipkin.base-url=http://localhost:8112
按照上述步骤分别改动两个服务,然后重新启动即可。
4.3、验证
当三个服务都启动后,我们请求一次http://localhost:8111/feignApi 接口,然后从sleuth-span1和sleuth-span2两个服务的控制台看到如下内容:
#sleuth-span2服务
2020-11-20 09:32:26.364 INFO [sleuth-span2,3355489db5474129,3355489db5474129,true] 55120 --- [nio-8111-exec-1] com.qriver.cloud.consumer : 执行消费者FeignController的feignApi()方法!
#sleuth-span1服务
2020-11-20 09:32:26.922 INFO [sleuth-span1,3355489db5474129,8d044e806d17fac1,true] 27116 --- [nio-8110-exec-1] com.qriver.cloud.provider : 调用服务提供者ProviderController的provider()方法!
然后我们知道了这次请求的traceId为3355489db5474129,然后,在Zipkin的可视化界面中的右上角输入该traceId,点击“搜索”查看请求链路情况。从图中我们可以看出,sleuth-span2服务的/feignapi接口,调用了sleuth-span1服务的/provider接口。
然后,点击其中一次微服务调用,可以看到如下内容,显示了这次链路请求中每个span的使用时间和其他一些信息。
5、抽样采集
前面,我们已经实现了对分布式系统中的请求跟踪,同时这些信息可以通过zipkin进行了收集和存储,最终实现对分布式系统的监控和分析功能。现在问题出现了,我们是否需要针对每次请求进行请求跟踪,如果每次请求都被跟踪的话,对性能的影响有如何呢?其实,这些问题Sleuth已经为我们考虑了,在Sleuth设计中,采用了抽象收集的方式来为跟踪信息打上收集标记,底层的抽样收集策略是通过Sampler接口实现的。
在实践中,有两种设置抽样采集的方式,首先第一种就是通过在application.properties中添加如下配置信息:
#采集百分比
spring.sleuth.sampler.percentage=1
然后,可以通过注入实体对象,进行抽样采集控制,比如:
@Bean
public AlwaysSampler defaultSampler() {
return new AlwaysSampler();
}
如同AlwaysSampler类,我们也可以通过实现Sampler接口,自定义采样策略。