九、服务链路追踪Spring Cloud Sleuth

  Spring Cloud Sleuth是Spring Cloud的一个组件,它的主要功能是在分布式系统中提供服务链路追踪的解决方案。

一、Spring Cloud Sleuth介绍

  微服务架构是一个分布式架构,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性较高,如果出现了错误和异常,很难去定位。主要体现在一个请求可能需要调用很多个服务,而内部服务的调用复杂性决定了问题难以定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题能够快速定位的目的。
  目前,常见的链路追踪组件有Google的Dapper、Twitter的Zipkin,以及阿里的Eagleeye (鹰眼)等,它们都是非常优秀的链路追踪开源组件。
  这里主要讲述如何在Spring Cloud Sleuth中集成Zipkin,在Spring Cloud Sleuth中集成Zipkin非常简单,只需要引入相应的依赖并做相关的配置即可。

Zipkin介绍

  Zipkin是一个分布式跟踪系统:它可以帮助收集时间数据,解决在微服务架构下的延迟问题;它管理这些数据的收集和查找;Zipkin的设计是基于谷歌的Google Dapper论文。
  每个应用程序向Zipkin报告定时数据,Zipkin UI呈现了一个依赖图表来展示多少跟踪请求经过了每个应用程序;如果想解决延迟问题,可以过滤或者排序所有的跟踪请求,并且可以查看每个跟踪请求占总跟踪时间的百分比。

二、基本术语

Spring Cloud Sleuth 采用了Google的开源项目Dapper的专业术语。
(1) Span: 基本工作单元,发送一个远程调度任务就会产生一个Span,Span是用一个64位ID唯一标识的,Trace是用另一个64位ID唯一标识的。 Span还包含了其他的信息,例如摘要、时间戳事件、Span的ID以及进程ID。
(2) Trace:由一系列Span组成的,呈树状结构。请求一个微服务系统的API接口,这个API接口需要调用多个微服务单元,调用每个微服务单元都会产生一个新的Span, 所有由这个请求产生的Span组成了这个Trace。
(3) Annotation:用于记录一个事件,一些核心注解用于定义一个请求的开始和结束,这些注解如下。

  • cs-Client Sent: 客户端发送一个请求,这个注解描述了Span的开始。
  • sr-Server Received:服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳,
    便可得到网络传输的时间。
  • ss-Server Sent:服务端发送响应,该注解表明请求处理的完成(当请求返回客户端) ,
    用ss的时间戳减去sr时间戳,便可以得到服务器请求的时间。
  • er-Client Received: 客户端接收响应, 此时Span结束,如果er的时间戳减去cs时间
    戳,便可以得到整个请求所消耗的时间。

三、代码实现

这里继续在之前的工程项目中实现,使用eureka-server Module作为服务注册中心,新建zipkin-server Module作为链路追踪服务中心,负责存储链路数据。gateway-service Module作为服务网关工程,负责请求的转发,同时它也作为链路追踪客户端,负责产生链路数据,并上传给 zipkin-server。 eureka-producer Module是一个服务提供者,对外暴露API接口,同时它也作为链路追踪客户端,负责产生链路数据。

3.1 构建Zipkin-Server

新建一个 Module工程,取名为 zipkin-server,工程的pom文件继承了主Maven工程的pom文件,并引入Eureka的起步依赖spring-cloud-starter-eureka、Zipkin Server的依赖zipkin-server,以及Zipkin Server的UI界面依赖zipkin-autoconfigure-ui。后两个依赖提供了Zipkin的 功能和Zipkin界面展示的功能 ,代码如下 :

<parent>
        <groupId>com.hand</groupId>
        <artifactId>macro-service</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> 
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
        </dependency>
    </dependencies>

在程序的启动类ZipkinServiceApplication加上@EnableZipkinServer 注解,开启ZipkinServer 的功能,加上@EnableEurekaClient注解, 启动EurekaClient,代码如下:

@SpringBootApplication
@EnableEurekaClient
@EnableZipkinServer
public class ZipkinServerApplication {

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

}

在程序的配置文件application.yml文件做程序的相关配置,指定程序名为zipkin-server, 端口号为9411, 服务注册地址为http://localhost:8671/eureka/,代码如下:

spring:
  application:
    name: zipkin-server
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8671/eureka/

server:
  port: 9411
3.2 构建Eureka-Producer

在主Maven工程下建一个Module工程 ,取名为eureka-producer,作为服务提供者,对外暴露API接口。eureka-producer工程的pom文件继承了主Maven工程的pom文件,并引入了Eureka的起步依赖spring-cloud-starter-eureka、Web起步依赖 spring-boot-starter-web,以及 Zipkin 的起步依赖 spring-cloud-starter-zipkin,代码如下:

 <parent>
        <groupId>com.hand</groupId>
        <artifactId>macro-service</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> 
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>RELEASE</version>
        </dependency>
    </dependencies>

在程序的配置文applicatiom.yml中,指定程序名为eureka-producer,端口号为8672,服务
注册地址http://localhost:8671/eureka/, Zipkin Server地址为 http://localhost:9411。spring.sleuth.sampler.percentage为 1.0,即以100%的概率将链路的数据上传给Zipkin Server,在默认情况下,该值为 0.1。配置文件代码如下:

  client:
    service-url:
      defaultZone: http://localhost:8671/eureka/

spring:
  application:
    name: eureka-producer
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      percentage: 1.0

server:
  port: 8672

在 UserController类建一个“/hi”的 API接口,对外提供服务, 代码如下:

@RestController
public class UserController {

    @GetMapping("/hi")
    public String SayHi(){
        return "hi, i'm ben";
    }
}

最后作为Eureka Client, 需要在程序的启动类EurekaProducerApplication加上@EnableEurekaClient注解,开启Eureka Client的功能 。

3.3 构建Gateway-Service

新建一个名为gateway-service的工程,这个工程作为服务网关,将请求转发到eureka-producer。 作为Zipkin客户端 , 需要将链路数据上传给Zipkin Server,同时它也作为Eureka Client。在工程的pom文件中除了需要继承主Maven工程的pom文件,还需引入如下的依赖,代码如下:

<parent>
        <groupId>com.hand</groupId>
        <artifactId>macro-service</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath/> 
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>RELEASE</version>
        </dependency>
    </dependencies>

在工程的配置文件application.yml中,配置程序名为gateway-service,端口号为5000,服务注 册地址为http://localhost:8671/eureka/, Zipkin Server地址为http://localhost:9411 ,以 “/user-api/**"开头的Url请求转发到服务名为eureka-producer的服务,配置代码如下:

spring:
  application:
    name: gateway-service
  sleuth:
    sampler:
      percentage: 1.0
  zipkin:
    base-url: http://localhost:9411

server:
  port: 5000

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8671/eureka/

zuul:
  routes:
    user-api:
      path: "/user-api/**"
      serviceId: eureka-producer

在程序的启动类 GatewayServiceApplication 上加@EnableEurekaClient 注解 , Client的功能,加上@EnableZuulProxy注解, 开启 Zuul代理功能,代码如下:

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class GatewayServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
    }
}
3.4 项目演示

完整的项目搭建完毕,依次启动 eureka-server、zipkin-server、eureka-producer和gateway-service。在浏览器上访问http://localhost:5000/user-api/hi, 浏览器显示如下:

访问http://localhost:9411,即访问Zipkin的展示界面, 界面显示下图所示。

Zipkin Server的主页

这个界面用于展示Zipkin Server 收集的链路数据,可以根据服务名、开始时间、结束时间、 请求消耗的时间等条件来查找。 单击“FindTraces”按钮,界面如下图所示,从图中可知请 求的调用情况,例如请求的调用时间、消耗时间,以及请求调用的链路情况。


服务的链路数据展示

单击“ Dependences”按钮,可以查看服务的依赖关系。在本案例中,gateway-service 将
请求转发到了eureka-producer,这两个服务的依赖关系如下图所示。


服务的依赖关系

四、在链路数据中添加自定义数据

我们除了可以通过zipkin获取链路数据外,还可以项链路数据中添加自定义数据。本案例在gateway-service服务中实现 。在gateway-service工程里新建一个 ZuulFilter过滤器,它的类型为 post类型, order为900,开启拦截。在过滤器的拦截逻辑方法里 , 通过Tracer的addTag 方法加上自定义的数据,在这里我加上了链路的操作人。另外也可以在这个过滤器中获取当前链路的traceld信息, traceld作为链路数据的唯一标识,可以存储在log日志中,方便后续查找,这里只是将traceld信息简单地打印在控制台上。 代码如下:

@Component
public class LoggerFilter extends ZuulFilter{

    public static String PRE_TYPE = "pre";

    @Autowired
    Tracer tracer;

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 900;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        tracer.addTag("operator", "ben");
        System.out.println("traceId: " + tracer.getCurrentSpan().getTraceId());
        return null;
    }
}

重新部署项目,访问http://localhost:5000/user-api/hi,然后访问http://localhost:9411,可以看到操作人已经被添加到链路数据中了。如下图:

链路数据详情

后台也成功输出traceId

五、使用RabbitMQ传输链路数据

  从上面的程序可知,最终gateway-service收集的数据是通过Http方式上传给zipkin-server的。但是在一些情况下微服务可能和Zipkin服务端网络不通(Zipkin服务端宕机),使用Http通信收集方式将无法工作。而使用消息组件完成微服务和Zipkin服务端的通信,既实现了微服务和Zipkin服务端的解耦,微服务不需要知道Zipkin服务端的网络地址,同时也避免因Zipkin服务器宕机而使收集数据的工作无法进行的问题。由于在Spring Cloud Sleuth中支持消息组件来传输链路数据,本节使用RabbitMQ来传输链路数据。
首先改造zipkin-server工程 , 在其pom文件中将zipkin-server的依赖去掉,加上spring-cloud-sleuth-zipkin-stream和 spring-cloud-starter-stream-rabbit 的依赖,代码如下:

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

在工程的配置文件application.yml加上RabbitMQ的配置,包括host、端口、用户名、密
码,代码如下:

spring:
  rabbitmq:
    host : localhost
    port: 5672
    username : guest
    password : guest

在程序的启动类ZipkinServerApplication中加上@EnableZipkinStreamServer注解,开启 ZipkinStreamServer,代码如下:

@SpringBootApplication
@EnableEurekaClient
@EnableZipkinStreamServer
public class ZipkinServerApplication {

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

下面改造Zipkin Client (包括 gateway-service工程和eureka-producer工程),在他们的pom文件中将 spring-cloud-starter-zipkin依赖改为 spring-cloud-sleuth-zipkin-stream和spring-cloud-starter­-stream-rabbit,代码如下:

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

同时在配置文件 applicayion.yml加上RabbitMQ的配置,同zipkin-server工程。这样,就将链路的上传数据得方式从Http改为用消息组件RabbitMQ。

六、ElasticSearch中存储链路数据

  由于Zipkin Server将数据存储在内存中, 一旦程序重启,之前的链路数据全部丢失,因此需要将链路数据持久化存储。 Zipkin支持将链路数据存储在MySQL、Elasticsearch 和Cassandra数据库中。这里使用Elasticsearch存储,在并发高、大数据的情况下,使用Elasticsearch存储链路数据有明显优势,并且支持分布式文件实时存储。需要安装ElasticSearch和Kibana (下一节中使用),下载地址为https://www.elastic.co/products/elasticsearch。安装完成后启动, 其中ElasticSearch的默认端口号为9200, Kibana的默认端口号为5601。
\color{red}{注:由于ElasticSearch和Kibana的版本关联性较强,这里我安装的是ElasticSearch5.6和Kibana5.6}
  这里直接在zipkin-server的基础上进行改造。首先在pom文件中加上zipkin的依赖和zipkin-autoconfigure-storage-elasticsearch-http的依赖,代码如下:

<dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
            <version>1.28.0</version>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-storage-elasticsearch-http</artifactId>
            <version>1.28.0</version>
</dependency>

  在程序的配置文件application.yml 中加上Zipkin的配置,配置了zipkin的存储类型(type) elasticsearch,使用的存储组件( StorageComponent ) 为elasticsearch,然后需要配置为elasticsearch,包括hosts (可以配置多个,用“” 隔开) 和index等。具体配置代码如下:

zipkin:
  storage:
    type: elasticsearch
    StorageComponent: elasticsearch
    elasticsearch:
      cluster: elasticsearch
      max-requests: 30
      index: zipkin
      index-shards: 3
      index-replicas: 1
      hosts: localhost:9200

只需要这些配置, Zipkin Server的链路数据就存储在 ElasticSearch中了。

七、用Kibana展示链路数据

  上一节讲述了如何将链路数据存储在ElasticSearch中,ElasticSearch可以和Kibana结合,将链路数据展示在Kibana上。安装完成Kibana后启动,Kibana默认会向本地端口为9200的ElasticSearch读取数据。Kibana默认的端口为5601,访问Kibana的主页http://localhost:5601,其界面如下图所示。

新增索引
创建索引

在上图的界面中, 单击 “Management”按钮,然后单击 “CreateIndexPattern”, 添加一个 index。这里将在上节ElasticSearch中写入链路数据的index配 置为“zipkin”,那么在界面填写为“zipkin-*”,完成索引创建。创建完成index后,单击“Discover”,就可以在界面上展示链路数据了,展示界面如下图所示。

Kibana数据查询界面

点击数据详情,可以看到整个链路数据的详细数据,还可以看到自定义添加的链路数据。如下图所示。


链路数据详情

总结:这一章节主要学习了Sleuth的概念和使用,与zuul结合进行网关路由和服务转发以实现服务链路,和zipkin结合进行链路数据保存和处理,与ElasticSearch和kibana结合进行链路数据的持久化处理和可视化操作。
github源码地址

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