随着分布式服务架构的流行,特别是微服务等设计理念在系统中的应用,系统规模也会变得越来越大,各个微服务间的调用关系也变得越来越复杂。通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果。在复杂的微服务架构系统中,几乎每一个前端请求都会形成一个复杂的分布式服务调用链路。那么就带来一系列问题,在业务规模不断增大、服务不断增多以及频繁变更的情况下,如何快速发现问题?如何判断故障影响范围?如何梳理服务依赖以及依赖的合理性?如何分析链路性能问题以及实时容量规划?面对上面这些问题,Spring Cloud Sleuth提供了分布式服务跟踪解决方案。
我们先在之前的EurekaDiscovery,EurekaDiscovery2两个实例中添加spring-cloud-starter-sleuth 依赖,EurekaDiscovery2会调用EurekaDiscovery中的接口,我们调用一下会发现日志中多了下面的信息。
INFO [eurekadiscovery2,dfff92d38bee8f43,7fec52e6eff9fd5a,false]
第一个值:它表示应用的名称,也就是配置文件spring.application.name的值。
第二个值:它是SpringCloudSleuth生成的一个ID,称为Trace ID,它用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID。
第三个值:它是SpringCloudSleuth生成的另外一个ID,称为Span ID,它表示一个基本的工作单元,比如发送一个http请求。
第四个值:false,表示是否要将该信息输出到Zipkin等服务中来收集和展示。
我们下面就用Zipkin-Server来收集展示日志及调用关系
Zipkin是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计基于 Google Dapper论文。
应用程序用于向Zipkin报告时序数据。Zipkin UI还提供了一个依赖关系图,显示了每个应用程序通过的跟踪请求数。如果要解决延迟问题或错误,可以根据应用程序,跟踪长度,注释或时间戳对所有跟踪进行筛选或排序。选择跟踪后,您可以看到每个跨度所需的总跟踪时间百分比,从而可以识别问题应用程序。
跟踪器存在于您的应用程序中,并记录有关发生的操作的时间和元数据。他们将数据发送到Zipkin的检测应用程序中的组件称为Reporter。通过几种传输之一将跟踪数据发送到Zipkin收集器,这些收集器将跟踪数据保存到存储中。稍后,API会查询存储以向UI提供数据。
下图来自官方流程图
下面我们布署一下ZipKin-Server。
在spring Cloud更加高版本的时候,已经不需要自己构建Zipkin Server了,只需要下载jar即可,下载地址:
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
启动ziokin-server服务器
java -jar zipkin-server-2.12.6-exec
********
** **
* *
** **
** **
** **
** **
********
****
****
**** ****
****** **** ***
****************************************************************************
******* **** ***
**** ****
**
**
***** ** ***** ** ** ** ** **
** ** ** * *** ** **** **
** ** ***** **** ** ** ***
****** ** ** ** ** ** ** **
:: Powered by Spring Boot :: (v2.1.3.RELEASE)
......
2019-03-30 11:16:30.677 INFO 1216 --- [oss-http-*:9411] c.l.a.s.Server : Serving HTTP at /0:0:0:0:0:0:0:0:9411 - http://127.0.0.1:9411/
2019-03-30 11:16:30.679 INFO 1216 --- [ main] c.l.a.s.ArmeriaAutoConfiguration : Armeria server started at ports: {/0:0:0:0:0:0:0:0:9411=ServerPort(/0:0:0:0:0:0:0:0:9411, [http])}
2019-03-30 11:16:30.898 INFO 1216 --- [ main] c.d.d.core : DataStax Java driver 3.7.1 for Apache Cassandra
2019-03-30 11:16:31.468 INFO 1216 --- [ main] c.d.d.c.GuavaCompatibility : Detected Guava >= 19 in the classpath, using modern compatibility layer
2019-03-30 11:16:32.768 INFO 1216 --- [ main] c.d.d.c.Native : Could not load JNR C Library, native system calls through this library will not be available (set this logger leve
l to DEBUG to see the full stack trace).
2019-03-30 11:16:32.768 INFO 1216 --- [ main] c.d.d.c.ClockFactory : Using java.lang.System clock to generate timestamps.
2019-03-30 11:16:33.680 INFO 1216 --- [ main] z.s.ZipkinServer : Started ZipkinServer in 13.294 seconds (JVM running for 18.331)
上图我们启动成功后,http://127.0.0.1:9411/可以访问
我们先在之前的EurekaDiscovery,EurekaDiscovery2两个实例中去掉spring-cloud-starter-sleuth 依赖,并且引入org.springframework.cloud:spring-cloud-starter-zipkin,因为spring-cloud-starter-zipkin依赖了spring-cloud-starter-sleuth
我们前面EurekaDiscovery2调用了EurekaDiscovery中的hi接口,现在我们EurekaDiscovery也添加一个接口调用EurekaDiscovery2中的sayHello接口, 具体方法可以参考前面的文章。
下面我们还需要添加一下配置。配置了我们zipkin-server的地址。我们还配上了采样的百分比。因为是测试所以我配的是的100%
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1.0 #是监控的百分比,默认的是0.1表示10%,这里给1.0表示全部监控
下面我们启动一下实例,并且相互调用一下接口。
我们看一下日志,第四个值为true,说明已经将该信息输出到Zipkin服务收集和展示
INFO [eurekadiscovery,214f778588512f21,214f778588512f21,true]
INFO [eurekadiscovery2,1cfa4b0e6ced590f,aa540eeef769563b,true]
我们再看一下http://127.0.0.1:9411/,可以成功看到EurekaDiscovery,EurekaDiscovery2相互调用的数据。相互依赖关系等等。
我们还可以查看一下Json源数据。
[
{
"traceId": "214f778588512f21",
"parentId": "214f778588512f21",
"id": "6963d7120f59038e",
"kind": "CLIENT",
"name": "get",
"timestamp": 1553916829977886,
"duration": 389299,
"localEndpoint": {
"serviceName": "eurekadiscovery",
"ipv4": "192.168.0.107"
},
"tags": {
"http.method": "GET",
"http.path": "/sayHello"
}
},
{
"traceId": "214f778588512f21",
"id": "214f778588512f21",
"kind": "SERVER",
"name": "get /sayhello",
"timestamp": 1553916828980303,
"duration": 1465164,
"localEndpoint": {
"serviceName": "eurekadiscovery",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv6": "::1",
"port": 51716
},
"tags": {
"http.method": "GET",
"http.path": "/sayHello",
"mvc.controller.class": "HelloController",
"mvc.controller.method": "sayHello"
}
},
{
"traceId": "214f778588512f21",
"parentId": "214f778588512f21",
"id": "6963d7120f59038e",
"kind": "SERVER",
"name": "get /sayhello",
"timestamp": 1553916830132898,
"duration": 144336,
"localEndpoint": {
"serviceName": "eurekadiscovery2",
"ipv4": "192.168.0.107"
},
"remoteEndpoint": {
"ipv4": "169.254.33.162",
"port": 51727
},
"tags": {
"http.method": "GET",
"http.path": "/sayHello",
"mvc.controller.class": "HelloController",
"mvc.controller.method": "sayHello"
},
"shared": true
}
]
traceId:标记一次请求的跟踪,相关的Spans都有相同的traceId;
id:span id;
name:span的名称,一般是接口方法的名称;
parentId:可选的id,当前Span的父Span id,通过parentId来保证Span之间的依赖关系,如果没有parentId,表示当前Span为根Span;
timestamp:Span创建时的时间戳,使用的单位是微秒(而不是毫秒),所有时间戳都有错误,包括主机之间的时钟偏差以及时间服务重新设置时钟的可能性,
出于这个原因,Span应尽可能记录其duration;
duration:持续时间使用的单位是微秒(而不是毫秒);
annotations:注释用于及时记录事件;有一组核心注释用于定义RPC请求的开始和结束;
-cs:Client Send,客户端发起请求;
-sr:Server Receive,服务器接受请求,开始处理;
-ss:Server Send,服务器完成处理,给客户端应答;
-cr:Client Receive,客户端接受应答从服务器;
-binaryAnnotations:二进制注释,旨在提供有关RPC的额外信息。