46. 简介

Spring Cloud Sleuth 为Spring Cloud实现了分布式跟踪解决方案。

46.1 术语

Spring Cloud Sleuth 借用小巧玲珑术语。

**跨度:**基本工作单位。例如,发送 RPC 是一个新的 span,就像向 RPC 发送响应一样。 Span 由 span 的唯一 64-bit ID 和 span 所属的跟踪的另一个 64-bit ID 标识。 Spans 还有其他数据,例如描述,带时间戳的 events,key-value 注释(标签),导致它们的 span 的 ID,以及 process ID(通常是 IP 地址)。

Spans 启动和停止,他们跟踪他们的时间信息。创建 span 后,必须在将来某个时候停止它。

启动跟踪的初始 span 称为root span。 span 的 span id 的 value 等于 trace id。

**跟踪:**一组构成 tree-like 结构的 spans。例如,如果您正在运行分布式 big-data store,则可能会由 put 请求形成跟踪。

**注释:**用于记录 time 中 event 的存在。用于定义请求的开始和停止的一些核心注释是:

  • cs - Client 已发送 - client 已发出请求。这个 annotation 描述了 span 的开始。

  • sr - 服务器已收到 - 服务器端获得了请求并将开始处理它。如果从该时间戳中减去 cs 时间戳,则会收到网络延迟。

  • ss - 服务器已发送 - 在请求处理完成时注释(当响应被发送回 client 时)。如果从该时间戳中减去 sr 时间戳,则将接收服务器端所需的 time 来处理请求。

  • cr - 收到的 Client - 表示 span 结束。 client 已成功收到服务器端的响应。如果从该时间戳中减去 cs 时间戳,则会收到 client 从服务器接收响应所需的整个 time。

可视化SpanTrace将与 Zipkin annotations 一起显示在系统中:

跟踪信息传播

音符的每种颜色表示 span(7 spans - 从AG)。如果您在备注中有此类信息:

Trace Id = X
Span Id = D
Client Sent

这意味着当前 span 将Trace-Id设置为XSpan-Id设置为D。它还发出了Client Sent event。

这就是 spans 的 parent/child 关系的可视化效果如下:

Parent 孩子的关系

46.2 目的

在以下部分中,将考虑上图中的 example。

46.2.1 使用 Zipkin 进行分布式跟踪

总共有7 spans。如果你去 Zipkin 中的痕迹,你会在第二个痕迹中看到这个数字:

痕迹

但是,如果您选择特定的跟踪,那么您将看到4 spans

跟踪信息传播

选择特定跟踪时,您将看到合并的 spans。这意味着,如果有 2 个 spans 发送到 Zipkin,服务器已接收并且服务器已发送/ Client 已接收且 Client 已发送注释,则它们将显示为单个 span。

在这种情况下,为什么 7 和 4 spans 之间存在差异?

  • 2 spans 来自http:/start span。它具有服务器接收(SR)和服务器发送(SS)注释。

  • 2 spans 来自从service1service2http:/foo端点的 RPC 调用。它在service1侧有 Client Sent(CS)和 Client Received(CR)注释。它还在service2端具有服务器接收(SR)和服务器发送(SS)注释。物理上有 2 个 spans 但它们形成一个与 RPC 调用相关的逻辑 span。

  • 2 spans 来自从service2service3http:/bar端点的 RPC 调用。它在service2侧有 Client Sent(CS)和 Client Received(CR)注释。它还在service3端具有服务器接收(SR)和服务器发送(SS)注释。物理上有 2 个 spans 但它们形成一个与 RPC 调用相关的逻辑 span。

  • 2 spans 来自从service2service4http:/baz端点的 RPC 调用。它在service2侧有 Client Sent(CS)和 Client Received(CR)注释。它还在service4端具有服务器接收(SR)和服务器发送(SS)注释。物理上有 2 个 spans 但它们形成一个与 RPC 调用相关的逻辑 span。

因此,如果我们计算物理 spans,我们从http:/start **2service1调用service22形式service2调用service32service2调用service4.总共7 spans。

从逻辑上讲,我们看到了Total Spans:4的信息,因为我们有1 span 与service1的传入请求和与 RPC calls 相关的3 spans 有关。

46.2.2 可视化错误

Zipkin 允许您可视化跟踪中的错误。当抛出 exception 并且没有被捕获时,我们在 span 上设置了正确的标签,Zipkin 可以正确地着色。您可以在跟踪列表中看到一条红色的迹线。那是因为抛出了一个 exception。

如果单击该跟踪,则会看到类似的图片

错误跟踪

然后,如果您单击其中一个 spans,您将看到以下内容

错误跟踪信息传播

如您所见,您可以轻松地查看错误的原因以及与之相关的整个堆栈跟踪。

46.2.3 实例

图 1_.单击 Pivotal Web Services 图标直播!

Zipkin 部署在 Pivotal Web Services 上

Zipkin 中的依赖关系图如下所示:

依赖

图 1_.单击 Pivotal Web Services 图标直播!

Zipkin 部署在 Pivotal Web Services 上

46.2.4 Log 相关

当通过 trace id 等于 e.g 来查看这四个 applications 的日志时。 2485ec27856c56f4会得到以下内容:

service1.log:2016-02-26 11:15:47.561  INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application   : Hello from service1. Calling service2
service2.log:2016-02-26 11:15:47.710  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Hello from service2. Calling service3 and then service4
service3.log:2016-02-26 11:15:47.895  INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application   : Hello from service3
service2.log:2016-02-26 11:15:47.924  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Got response from service3 [Hello from service3]
service4.log:2016-02-26 11:15:48.134  INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application   : Hello from service4
service2.log:2016-02-26 11:15:48.156  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Got response from service4 [Hello from service4]
service1.log:2016-02-26 11:15:48.182  INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application   : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]

如果您正在使用,Splunk等 log 聚合工具,则可以对发生的 events 进行排序。 Kibana 的示例如下所示:

Log 与 Kibana 的关联

如果你想使用Logstash这里是 Logstash 的 Grok pattern:

filter {
       # pattern matching logback pattern
       grok {
              match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
       }
}

如果要将 Grok 与 Cloud Foundry 中的日志一起使用,则必须使用此 pattern:

filter {
       # pattern matching logback pattern
       grok {
              match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
       }
}

使用 Logstash JSON Logback

通常,您不希望将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中。为此,您必须执行以下操作(为了便于阅读,我们将以groupId:artifactId:version表示法传递依赖项。

依赖关系设置

  • 确保 Logback 位于 classpath(ch.qos.logback:logback-core)上

  • 添加 Logstash Logback 编码 - version 为 version 4.6net.logstash.logback:logstash-logback-encoder:4.6

Logback 设置

您可以在下面找到 Logback configuration(名为logback-spring.xml的文件)的示例:

  • 将来自 application 的信息以 JSON 格式记录到build/${spring.application.name}.json文件中

  • 已经注释掉了另外两个 appenders --console 和标准 log 文件

  • 具有与上一节中介绍的相同的 logging pattern

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
	
	<springProperty scope="context" name="springAppName" source="spring.application.name"/>
	<!-- Example for logging into the build folder of your project -->
	<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>

	<!-- You can override this to have a custom pattern -->
	<property name="CONSOLE_LOG_PATTERN"
			  value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

	<!-- Appender to log to console -->
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<!-- Minimum logging level to be presented in the console logs-->
			<level>DEBUG</level>
		</filter>
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
			<charset>utf8</charset>
		</encoder>
	</appender>

	<!-- Appender to log to file -->
	<appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${LOG_FILE}</file>
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
			<maxHistory>7</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
			<charset>utf8</charset>
		</encoder>
	</appender>
	
	<!-- Appender to log to file in a JSON format -->
	<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<file>${LOG_FILE}.json</file>
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
			<maxHistory>7</maxHistory>
		</rollingPolicy>
		<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
			<providers>
				<timestamp>
					<timeZone>UTC</timeZone>
				</timestamp>
				<pattern>
					<pattern>
						{
						"severity": "%level",
						"service": "${springAppName:-}",
						"trace": "%X{X-B3-TraceId:-}",
						"span": "%X{X-B3-SpanId:-}",
						"parent": "%X{X-B3-ParentSpanId:-}",
						"exportable": "%X{X-Span-Export:-}",
						"pid": "${PID:-}",
						"thread": "%thread",
						"class": "%logger{40}",
						"rest": "%message"
						}
					</pattern>
				</pattern>
			</providers>
		</encoder>
	</appender>
	
	<root level="INFO">
		<appender-ref ref="console"/>
		<!-- uncomment this to have also JSON logs -->
		<!--<appender-ref ref="logstash"/>-->
		<!--<appender-ref ref="flatfile"/>-->
	</root>
</configuration>

如果您使用自定义logback-spring.xml,则必须在bootstrap而不是application property 文件中传递spring.application.name。否则,您的自定义 logback 文件将无法正确读取 property。

46.2.5 传播 Span Context

span context 是 state,它必须传播到 process 边界的任何 child Spans。 Span Context 的一部分是 Baggage。 trace 和 span ID 是 span context 的必需部分。 Baggage 是一个可选部分。

Baggage 是存储在 span context 中的一组 key:value 对。 Baggage 与痕迹一起旅行,并附在每个 span 上。 Spring Cloud Sleuth 将理解,如果 HTTP 标头以baggage-为前缀,则标头与 baggage 相关,对于消息,它以baggage_开头。

目前,baggage 物品的数量或大小没有限制。但是,请记住,太多可能会降低系统吞吐量或增加 RPC 延迟。在极端情况下,由于超过 transport-level 消息或标头容量,它可能会导致应用程序崩溃。

在 span 上设置 baggage 的示例:

Span initialSpan = this.tracer.createSpan("span");
initialSpan.setBaggageItem("foo", "bar");
initialSpan.setBaggageItem("UPPER_CASE", "someValue");

Baggage vs. Span Tags

Baggage 带着痕迹行进(i.e.每个 child span 都包含 parent 的行李)。 Zipkin 不了解 baggage,甚至不会收到这些信息。

标签附加到特定的 span - 它们仅针对特定的 span 呈现。但是,您可以按标记搜索以查找跟踪,其中存在具有搜索标记 value 的 span。

如果您希望能够基于 baggage 查找 span,则应在 root span 中添加相应的条目作为标记。

@Autowired Tracer tracer;

Span span = tracer.getCurrentSpan();
String baggageKey = "key";
String baggageValue = "foo";
span.setBaggageItem(baggageKey, baggageValue);
tracer.addTag(baggageKey, baggageValue);

46.3 添加到项目中

要确保__ip name 在 Zipkin 中正确显示,请在bootstrap.yml中设置spring.application.name property。

46.3.1 只 Sleuth(log 相关)

如果你只想从没有 Zipkin integration 的 Spring Cloud Sleuth 中获利,只需将spring-cloud-starter-sleuth模块添加到你的项目中。

Maven 的.

<dependencyManagement> 
         <dependencies>
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-dependencies</artifactId>
                 <version>${release.train.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
   </dependencyManagement>

   <dependency> 
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-sleuth</artifactId>
   </dependency>

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-sleuth
摇篮.

dependencyManagement { 
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies { 
    compile "org.springframework.cloud:spring-cloud-starter-sleuth"
}

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-sleuth

通过 HTTP 使用 Zipkin 46.3.2 Sleuth

如果你想要 Sleuth 和 Zipkin,只需添加spring-cloud-starter-zipkin依赖项。

Maven 的.

<dependencyManagement> 
         <dependencies>
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-dependencies</artifactId>
                 <version>${release.train.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
   </dependencyManagement>

   <dependency> 
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zipkin</artifactId>
   </dependency>

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-zipkin
摇篮.

dependencyManagement { 
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies { 
    compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-zipkin

46.3.3 Sleuth 与 Zipkin 通过 RabbitMQ 或 Kafka

如果要使用 RabbitMQ 或 Kafka 而不是 http,请添加spring-rabbitspring-kafka依赖项。默认目标 name 是zipkin

注意:spring-cloud-sleuth-stream已弃用且与这些目标不兼容

如果你想通过 RabbitMQ Sleuth 添加spring-cloud-starter-zipkinspring-rabbit依赖项。

Maven 的.

<dependencyManagement> 
         <dependencies>
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-dependencies</artifactId>
                 <version>${release.train.version}</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
         </dependencies>
   </dependencyManagement>

   <dependency> 
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-zipkin</artifactId>
   </dependency>
   <dependency> 
       <groupId>org.springframework.amqp</groupId>
       <artifactId>spring-rabbit</artifactId>
   </dependency>

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-zipkin - 这样将下载所有依赖的依赖项

要自动配置 rabbit,只需添加 spring-rabbit 依赖项
摇篮.

dependencyManagement { 
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies {
    compile "org.springframework.cloud:spring-cloud-starter-zipkin" 
    compile "org.springframework.amqp:spring-rabbit" 
}

在 order 中,不要自己选择版本,如果通过 Spring BOM 添加依赖项 management 会更好

将依赖项添加到spring-cloud-starter-zipkin - 这样将下载所有依赖的依赖项

要自动配置 rabbit,只需添加 spring-rabbit 依赖项