随着业务的发展, 系统规模也会变 得越来越大, 各微服务间的调用关系也变得越来越错综复杂。 通常一个由客户端发起的请 求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果, 在复杂的微服 务架构系统中, 几乎每一个前端请求都会形成一条复杂的分布式服务调用链路, 在每条链 路中任何一个依赖服务出现延迟过高或错误的时候都有可能引起请求最后的失败。这时候, 对于每个请求, 全链路调用的跟踪就变得越来越重要, 通过实现对请求调用的跟踪可以帮 助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等。
只需在服务的 pom.xrnl 依赖管理中增加 spring-cloud-starter-sleuth 依赖
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-sleuth</artifactid>
</dependency>
假设我们现在有trace-1、trace-2和eureka-server三个微服务应用,并且trace-1、trace-2都关联eureka-server。
并且trace-1写一个接口,在接口中,调用trace-2,当我们请求trace-1的接口时,我们看到日志信息如下:
-- trace-1
INFO [trace-1, f410ab57afd5cl45, a9f2118fa2019684, false) 25028 --- [nio-9101-exec-l] ication$$EnhancerBySpringCGLIB$$d8228493 : ===<call trace-1>===
-- trace-2
INFO [trace-2, f 410ab57afd5cl45, e9a377dc2268bc29, false J 23112 --- [nio-9102-exec-l] ication$$EnhancerBySpringCGLIB$$e6cb4078 : ===<call trace-2>===
从上面的控制台输出内 容 中 , 我 们可 以看到多了 一 些形如[trace-1, f410ab57afd5c145, a9f2118fa2019684, false]的日志信息, 而这些元素正是实现分布式服务跟踪的重要组成部分, 每个值的含义如下所述。
• 第一个值: trace-1, 它记录了应用的名称,也就是 application.properties中 spring.application.name参数配置的属性。
• 第二个值: f410ab57afd5c145, Spring Cloud Sleuth生成的一个ID,称为Trace ID,它用来标识一条请求链路。 一条请求链路中包含一个TraceID, 多个SpanID。
• 第三个值: a9f2118fa2019684, Spring Cloud Sleuth生成的另外一个 ID, 称为Span ID, 它表示一个基本的工作单元, 比如发送一个HTTP请求。
• 第四个值: false, 表示是否要将该信息输出到Zipkin等服务中来收集和展示 。
上面四个值中的Trace ID和SpanID是Spring Cloud Sleuth实现分布式服务跟踪的核心。 在一次服务请求链路的调用过程中, 会保待并传递同一个Trace ID, 从而将整个分布于不同微服务进程中的请求跟踪 信息串联起来。 以上面输出内容为例, trace-1 和trace-2同属于一个前端服务请求来源,所以它们的TraceID是相同的,处于同一条请求链路中。
跟踪原理
分布式系统中的服务跟踪在理论上并不复杂, 它主要包括下面两个关键点:
-
为了实现请求跟踪, 当请求发送到分布式系统的入口端点时, 只需要服务跟踪框架为该请求创建一个唯一的跟踪标识, 同时在分布式系统内部流转的时候,框架始终保待传递 该唯一标识, 直到返回给请求方为止, 这个唯一标识就是前文中提到的Trace ID。 通过TraceID的记录, 我们就能将所有请求过程的日志关联起来。
-
为了统计各处理单元的时间延迟, 当请求到达各个服务组件时, 或是处理逻辑到达某个状态时, 也通过一个唯一标识来标记它的开始、 具体过程以及结束, 该标识就是前文中提到的SpanID。 对于每个Span来说, 它必须有开始和结束 两个节点, 通过记录开始 Span和结束Span的时间戳,就能统计出该Span的时间延迟,除了时间戳记录之外, 它还可以包含一些其他元数据, 比如事件名称、 请求信息等。
通过 在工程中 引入 spring-cloud-starter-sleuth 依赖之后, 它会自动为当前应用构建起各通信通道的跟踪机制, 比如:
-
通过诸如 RabbitMQ、 Kafka C 或者其他任何 Spring Cloud Stream 绑定器实现的消息中间件) 传递的请求。
-
通过 Zuul 代理传递的请求。
-
通过 RestTemplate 发起的请求。
@RequestMapping(value = "/trace-2", method = RequestMethod.GET)
public String七race(HttpServletRequest request) {
logger.info("===<call trace-2, Traceid={}, Spanid={}>===", request.getHeader("X-B3-Traceid"), request.getHeader("X-B3-Spanid"));
return "Trace";
}
}
抽样收集
public interface Sampler {
/**
* @return true if the span is not null and should be exported to the tracing system
*/
boolean isSarnpled(Span span);
}
spring.sleuth.sampler.percentage=0.1
@Bean
public AlwaysSampler defaultSampler () (
return new AlwaysSampler();
}
public class TagSampler implements Sampler {
private String tag;
public TagSampler(String tag) {
this.tag= tag;
}
@Override
public boolean isSampled(Span span) (
return span.tags().get(tag) != null;
}
}
与Logstash整合
通过之前的准备与整合,我们已经为trace-1和trace-2引入了Spring Cloud Sleuth的基础模块spring-cloud-s七arter-sleuth, 实现了在各个微服务的日志信息中添加跟踪信息的功能。 但是, 由于日志文件都离散地存储在各个服务实例的文件系统之上, 仅仅通过查看日志文件来分析我们的请求链路依然是一件相当麻烦的事, 所以我们还需要一些工具来帮助集中收集、 存储和搜索这些跟踪信息。 引入基于日志的分析系统是一个不错的选择, 比如ELK平台, 它可以轻松地帮助我们收集和存储这些跟踪日志, 同时在需要的时候我们也 可以根据Trace ID来轻松地搜索出对应请求链路 相关的明细日志。
- ElasticSearch是一个开源分布式搜索引擎, 它的特点有: 分布式, 零配置, 自动发现, 索引自动分片, 索引副本机制,RESTful风格接口, 多数据源, 自动搜索负载等。
- Logstash是一个完全开源的工具, 它可以对日志进行收集、 过滤, 并将其存储供以后使用。
- Kibana 也是一个开源和免费的工具, 它可以为Logstash 和ElasticSearch提供日志分析友好的Web界面, 可以帮助汇总、 分析和搜索重要数据日志。
- 在pom.xml依赖中引入logstash丑ogback-encoder依赖, 具体如下:
<dependency>
<groupid>net.logstash.logback</groupid>
<artifactid>logstash-logback-encoder</artifactid>
<version>4.6</version>
</dependency>
- 在工程/resource 目录下创建 bootstrap.properties 配置文件,将 spring. application.name= trace-1 配置移动到该文件中去。由于 logback-spring.xml 的加载在 application.properties 之前, 所以之前的配置 logback-spring.xml 无法获取 spring.application.name 属性, 因此这里将该属性移动到最先加载的 bootstrap.properties 配置文件中。
-
在工程 /resource 目录下创建 logback 配置文件 logback-spring.xml, 具体内 容如下:
<?xml version="l. 0" encoding="UTF - 8"?>
<configura已on>
<include resource= "org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="springAppName" source="spring.
app让cation.name"/>
<'-- 日志在工程中的桧出位置 -->
<property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>
<!--控制台的日志捡出样式-->
<property name="CONSOLE_LOG_PATTERN"
value="%clr(%d(yyyy-MM-dd HH:mm:ss.SSS}) (faint}岩clr(${LOG_LEVEL_PATTERN:
-%Sp}) %clr([${springAppName:-},%X{X-B3-Traceid:-},%X(X-B3-Spanid:-},%X{X-Span-Expo
rt:-}]) (yellow}号clr(${PID:- }) (magenta} %clr(---) {fain七}毛clr([号15.15t)) { fain七) %c
lr(-40.40logger{39}) {cyan) %clr(:) (faint}告rn令n${LOG_EXCEPTI0N_CONVERSION一_WORD:-毛wE
x} "/>
<! -- 控制台Appender -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONS0LE_LOG—PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<' -- 为logstash输出的JSON格式的Appender 一>
<appender name=" logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_ FILE}. j son</ 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>
<times tamp>
<timeZone>UTC</timeZone>
</times七amp>
<pa七tern>
<pattern>
"severity": "%level",
"service" : "$ { springAppName: -} ",
"trace": "%X{X-B3-Traceid:-} ",
"span": "%X{X-B3-Spanid:-} ",
"exportable": "%X{X-Span-Export: 一} ” ,
"pid": "${PIO:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<rootlevel="INFO">
<appender-ref ref="console"/>
<appender-ref ref="logstash"/>
</root>
{" @timestamp":"2016-12-04T06:57:58.970+00:00","severity":"INFO","service":"trac
e-l","trace":"589ee5f7b860132f","span":"a9e891273affb7fc","exportable":"false","pid
":"19756","thread":"http-nio-9101-exec-1","class":"c.d.TraceApplication$$EnhancerBy
SpringCGLIB$$a9604da6","rest":"===<call trace-1>===" )
{"@timestamp":"2016-12-04T06:57:59.061+00:00","severity":"INFO","service":"trace-1","trace":"589ee5f7b860132f","span":"2df8511ddf3d79a2","exportable":"false","pid
":"19756","thread":"http-nio-9101-exec-l","class":"o.s.c.a.AnnotationConfigApplicat
ionContext","rest":"Refreshing org. springframework. context. annotation. AnnotationConfigApplicationContext@64951f38: startup date [Sun Dec 04 14: 57: 59 CST 2016]; parent:
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationCo.n
text@4b8c8f15"}
<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>127.0.0.1:9250</destination>
</appender>
与Zipkin整合
- Collector:收集器组件, 它主要处理从外部系统发送过来的跟踪信息, 将这些信息转换为Zip幻n内部处理的Span格式, 以支待后续的存储、 分析、 展示等功能。
- storage:存储组件,它主要处理收集器接收到的跟踪信息, 默认会将这些信息存储在内存中,我们也可以修改此存储策略, 通过使用其他存储组件将跟踪信息存储到数据库中。
- RESTfull:API组件, 它主要用来提供外部访问接口。 比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
- WEB UI:UI组件, 基于API组件实现的上层应用。 通过UI组件, 用户可以方便而又直观地查询和分析跟踪信息。
来源:oschina
链接:https://my.oschina.net/xiaoyoung/blog/3233058