Chapter eleven《SpringCloud微服务实战》

分布式服务跟踪: Spring Cloud Sleuth

什么是Sprng Cloud Sleuth

是对一个请求所经过的整个服务链的跟踪,帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈。

随着业务不断地发展,系统拆分导致系统调用链路愈发复杂一个前端请求可能最终需要调用很多次后端服务才能完成,当整个请求变慢或不可用时,我们是无法得知该请求是由某个或某些后端服务引起的,这时就需要解决如何快读定位服务故障点,以对症下药。于是就有了分布式系统调用跟踪的诞生。

Tips:业界分布式服务跟踪的理论基础主要来自于 Google 的一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,使用最为广泛的开源实现是 Twitter 的 Zipkin,为了实现平台无关、厂商无关的分布式服务跟踪,CNCF 发布了布式服务跟踪标准 Open Tracing。国内,淘宝的“鹰眼”、京东的“Hydra”、大众点评的“CAT”、新浪的“Watchman”、唯品会的“Microscope”、窝窝网的“Tracing”都是这样的系统。

一般的,一个分布式服务跟踪系统,主要有三部分:

数据收集、数据存储和数据展示。

根据系统大小不同,每一部分的结构又有一定变化。譬如,对于大规模分布式系统,数据存储可分为实时数据和全量数据两部分,实时数据用于故障排查(troubleshooting),全量数据用于系统优化;数据收集除了支持平台无关和开发语言无关系统的数据收集,还包括异步数据收集(需要跟踪队列中的消息,保证调用的连贯性),以及确保更小的侵入性;数据展示又涉及到数据挖掘和分析。虽然每一部分都可能变得很复杂,但基本原理都类似。

image

服务追踪的追踪单元是从客户发起请求(request)抵达被追踪系统的边界开始,到被追踪系统向客户返回响应(response)为止的过程,称为一个“trace”。

每个 trace 中会调用若干个服务,为了记录调用了哪些服务,以及每次调用的消耗时间等信息,在每次调用服务时,埋入一个调用记录,称为一个“span”。这样,若干个有序的 span 就组成了一个 trace。在系统向外界提供服务的过程中,会不断地有请求和响应发生,也就会不断生成 trace,把这些带有span 的 trace 记录下来,就可以描绘出一幅系统的服务拓扑图。附带上 span 中的响应时间,以及请求成功与否等信息,就可以在发生问题的时候,找到异常的服务;根据历史数据,还可以从系统整体层面分析出哪里性能差,定位性能优化的目标。

Spring Cloud Sleuth为服务之间调用提供链路追踪。通过Sleuth可以很清楚的了解到一个服务请求经过了哪些服务,每个服务处理花费了多长。从而让我们可以很方便的理清各微服务间的调用关系。此外Sleuth可以帮助我们:

  • 耗时分析: 通过Sleuth可以很方便的了解到每个采样请求的耗时,从而分析出哪些服务调用比较耗时;
  • 可视化错误: 对于程序未捕捉的异常,可以通过集成Zipkin服务界面上看到;
  • 链路优化: 对于调用比较频繁的服务,可以针对这些服务实施一些优化措施。

spring cloud sleuth可以结合zipkin,将信息发送到zipkin,利用zipkin的存储来存储信息,利用zipkin ui来展示数据。

Sleuth术语

因为Sleuth是根据Google的Dapper’s论文而来的,所以在术语上也借鉴了Dapper。

  • Span: 最基本的工作单元。例如: 发送一个RPC就是一个新的span,同样一次RPC的应答也是。Span通过一个唯一的,长度为64位的ID来作为标识,另外,再使用一个64位ID用于服务调用跟踪。Span也可以带有其他数据,eg:描述,时间戳,键值对标签,起始Span的ID,以及处理ID(通常使用IP地址)等等。 Span有起始和结束,它们用于跟踪时间信息。Span应该都是成对出现的,有始必有终,所以一旦创建了一个span,那就必须在未来某个时间点结束它。

提示: 起始的Span通常被称为:root span。它的id通常也被作为一个跟踪记录的id。

  • Trace: 一个树结构的Span集合。例如:在分布式大数据存储中,可能每一次请求都是一次跟踪记录。
  • Annotation: 用于记录一个事件的时间信息。一些基础核心的Annotation用于记录请求的起始和结束时间,例如:
    • cs: 客户端发送(Client Sent的缩写)。这个annotation表示一个span的起始;
    • sr: 服务端接收(Server Received的缩写)。表示服务端接收到请求,并开始处理。如果减去cs的时间戳,则可以计算出网络传输耗时。
    • ss: 服务端完成请求处理,应答信息被发回客户端(Server Sent的缩写)。如果减去sr的时间戳,则可以计算出服务端处理请求的耗时。
    • cr: 客户端接收(Client Received的缩写)。标志着Span的结束。客户端成功的接收到服务端的应答信息。如果减去cs的时间戳,则可以计算出请求的响应耗时。

下图,通过可视化的方式描述了Span和Trace的概念:

image

ZipKin

Zipkin是Twitter的一个开源项目,它基于 Google Dapper 实现。我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助查询跟踪数据以实现对分布式系统的监控程序,从而及时发现系统中出现的延迟升高问题并找出系统性能瓶颈的根源。除了面向开发的API接口之外,它还提供了方便的UI组件来帮助我们直观地搜索跟踪信息和分析请求链路明细,比如可以查询某段时间内各用户请求的处理时间等。

它主要由4个核心组件构成。

  • Collector:收集器组件,它主要处理从外部系统发送过来的跟踪信息,将这些信息转换为Zipkin内部处理的Span格式,以支待后续的存储、分析、展示等功能。
  • Storage:存储组件,它主要处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中。我们也可以修改此存储策略, 通过使用其他存储组件将跟踪信息存储到数据库中。
  • RESTful API: API组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
  • Web UI: UI组件,基于API组件实现的上层应用。通过UI组件,用户可以方便而又直观地查询和分析跟踪信息。

快速入门

创建 trace-1 9015

pom 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.zk.springcloud</groupId>
<artifactId>springcloud-trace-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springcloud-trace-1</name>
<description>Demo project for Spring Boot</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

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

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

</project>

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@RestController
@EnableDiscoveryClient
public class SpringcloudTrace1Application {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return  new RestTemplate();
    }

    @GetMapping(value = "/trace-1")
    public String trace(){
        System.out.println("===call trace-1===");
        return restTemplate().getForEntity("http://trace-2/trace-2",String.class).getBody();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudTrace1Application.class, args);
    }
}

配置文件

spring.application.name=trace-1
server.port=9015
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9004/eureka/

创建 trace-2 9016

pom 依赖同 trace-1 9015

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@EnableDiscoveryClient
public class SpringcloudTrace2Application {

    @GetMapping(value = "/trace-2")
    public String trace(){
        System.out.println("====== <call trace-2> ======");
        return "Trace";
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudTrace2Application.class, args);
    }
}

配置文件

spring.application.name=trace-2
server.port=9016
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9004/eureka/

将 eureka 9001 和 eureka-1 9004 、 trace-1 、 trace-2 都启动

访问 http://localhost:9015/trace-1 返回 Trace

访问 http://localhost:9016/trace-2 返回 Trace

日志打印

trace-1 

===call trace-1===

trace-2

====== <call trace-2> ======

实现跟踪

在 trace-1 9015 、trace-2 9016 添加如下依赖

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

重启后,调用 访问 http://localhost:9015/trace-1 返回 Trace

跟踪原理

对 trace-2 9016 输出头信息

@GetMapping(value = "/trace-2")
public String trace(HttpServletRequest request){
    System.out.println("====== <call trace-2> ====== traceid = " + request.getHeader("X-B3-TraceId")
+ " spanid = "+ request.getHeader("X-B3-SpanId"));
    return "Trace";
}

重启后,调用 访问 http://localhost:9015/trace-1 返回 Trace

为应用引入和配置 Zipkin 服务

trace-1 9015 、 trace-2 9016 添加 zipkin 依赖 和 配置

    <!-- 分布式链路追踪 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
        <version>1.2.5.RELEASE</version>
    </dependency>

配置

# zipkin 服务地址
spring.zipkin.base-url=http://localhost:9411
# #样本采集量,默认为0.1,为了测试这里修改为1,正式环境一般使用默认值。
spring.sleuth.sampler.probability=1

调用 http://localhost:9015/trace-1 可在 http://localhost:9411/zipkin 看到请求链路

消息中间件收集

修改客户端 trace-1 和 trace-2 添加 pom 依赖

<!-- 消息中间件  -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-sleuth-stream</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        <version>2.0.1.RELEASE</version>
    </dependency>

配置文件添加 rabbitmq 并将 zipkin 配置注释

# zipkin 服务地址
#spring.zipkin.base-url=http://localhost:9411
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=springcloud
spring.rabbitmq.password=123456

Less is more.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容