1.介绍

由于微服务很复杂,当服务多了之后,就需要有一个东西去记录服务之间的调用,这样可以快速定位问题,且最好能同时记录服务之间的调用时长等信息,方便优化系统。spring cloud sleuth就有这样的作用。我们称这种技术叫做服务链路追踪

2. sleuth

sleuth记录服务链路主要是依靠日志

2020-05-08 14:39:31.429  INFO [sleuth-track2,8f38aeed036ff375,21f05063bd635c35,false] 9236 --- [nio-8083-exec-1] c.t.c.s.controller.Controller            : hello track2

我在上面随便打印了一条它追踪日志,它在打印日志时,主要有这么几个组成部分:

第一个值:sleuth-track2,它记录了应用的名称,即spring.application.name

第二个值:8f38aeed036ff375,spring cloud sleuth生成的一个id,成为trace Id,它用来标识一条请求链路,一条请求链路包含一个Track Id,多个Span Id。一条服务链路上的track id是相同的

第三个值:21f05063bd635c35,spring cloud sleuth生成的另外一个id,成为span id,它表示一个基本的工作单元,比如发送一个http请求

第四个值:false ,表示是否要将该信息输出到Zipkin等服务中来收集和展示

3. 整合Spring Cloud Sleuth

创建3个项目:服务注册中心,sleuth-track1,sleuth-track2

服务注册中心就不说了,大致思路就是sleuth-track1使用feign调用sleuth-track2,然后我们打印个日志看下,服务之间的调用部分也不展示了

3.1 引入依赖

给sleuth-track1,sleuth-track2添加如下依赖即可

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

3.2 调用

调用sleuth-track1中的controller接口,然后该接口会使用feign调用sleuth-track2的接口,

注意:打印信息一定要使用log,不能用System.out.print,因为sleuth是基于log日志才操作的

3.3 结果

sleuth-track1:

2020-05-08 15:22:42.515  INFO [sleuth-track1,2c1215ce5b940d18,2c1215ce5b940d18,false] 14632 --- [nio-8082-exec-3] c.t.c.sleuthtack1.controller.Controller  : hello track1

sleuth-track2:

2020-05-08 15:22:42.553  INFO [sleuth-track2,2c1215ce5b940d18,2900b3e2e12a1326,false] 9236 --- [nio-8083-exec-6] c.t.c.s.controller.Controller            : hello track2

会发现他们的track id是相同的,span id是不同的

4. 追踪原理

主要包括两个关键点

  • 为了实现请求追踪,当请求发送到分布式系统的入口时,服务追踪框架即sleuth会为该请求创建一个唯一的跟踪标识,在分布式系统内部流转时,sleuth会始终保持该标识的唯一,知道返回给请求方。这个标识就是track id
  • 为了统计各处理单元的事件延迟,当请求到达某个组件时或处理逻辑到达某个状态时,也通过一个唯一标识来标记它的开始,具体过程以及结束。该标识就是span id,它具有开始和结束两个节点,通过记录开始span和结束span的时间戳,就能统计出该span的事件延迟,除了时间戳外,它还可以包含一些其他元数据,如事件名称,请求信息等

在spring boot应用中,通过在工程中引入spring-cloud-starter-sleuth依赖后,它会自动为当前应用构建各通信通道的追踪机制

比如:

  • rabbitmq,kafka传递的请求(或其他任何spring cloud stream绑定器实现的消息中间件)
  • 通过zuul代理传递的请求
  • 通过RestTemplate发起的请求

实现追踪就是在请求的请求头上添加实现追踪需要的重要信息。

5. 与Zipkin整合

zipkin分为服务端和客户端,zipkin server负责接受client传过来的数据,然后进行数据的处理与展示

zipkin client就是发送数据的

这里我们使用spring boot 1.5.8.RELEASE,spring cloud Dalston.SR4版本来写项目,版本问题引发的bug会在下面说明

5.1 创建Zipkin Server

5.1.1 创建一个spring cloud 工程,并引入下列依赖:

<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-server</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
</dependency>

 

5.1.2  添加注解

在启动类上添加注解@EnableZipkinServer

5.1.3 配置文件

server:
  port: 9411
spring:
  application:
    name: zipkin-server

5.1.4 启动并访问

启动项目,并访问http://localhost:9411/

5.2 zipkin client 

这个其实就是能产生追踪日志的客户端,spring cloud 的项目基本上都算是zipkin client,比如我们上面创建的sleuth-track1和sleuth-track2。

5.2.1 添加依赖

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

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

5.2.2 配置文件

#指定zipkin服务器的url地址
spring:
  zipkin:
    base-url: http://localhost:9411

5.2.3 修改配置

在第2节的时候,其中有个属性打印出来是false,该属性是表示是否要将该信息输出到Zipkin等服务中来收集和展示。

该属性的作用是抽样收集:理论上讲我们收集的追踪信息是越多越好,但是在高并发的分布式系统中,大量的请求调用会产生海量的追踪日志信息,如果过多的收集这些信息,会对整个系统的性能造成一定影响。所以我们要指定抽样收集

可通过配置来修改你的收集百分比:

spring.sleuth.sampler.percentage=0.1

默认值就是0.1,代表收集10%的请求追踪信息。

当我们本地调试时,可以设置为1,表示收集全部的追踪信息

5.2.4 新增bootstrap.yml

要确保您的应用程序名称在Zipkin中正确显示,请在bootstrap.yml中设置spring.application.name属性

5.2.5 启动项目,请求,查看 zipkin server

6. zipkin server和client版本不同而引起的bug

我一开始使用的spring cloud 版本:Hoxton.SR3,springboot版本:2.2.6.RELEASE,通过父pom中进行版本管理的,zipkin server和zipkin client都是其子模块。

zipkin server端:

当我引入zipkin-server和zipkin-autoconfigure-ui依赖时,pom文件直接报错,说找不到依赖,我一开始以为是没有指定zipkin版本的问题(我看书上写的也没指定版本),所以我就引入了zipkin的当前最高的一个版本号,就没有报错了,

接着启动,启动报错说是log框架太多了,导致启动失败,然后我查看了以下版本依赖,spring boot 默认使用的是log back,而zipkin默认使用的是log4j,然后我就在zipkin server的依赖中去除了它自己的日志框架。

再次启动,还是报错,说是tomcat embed什么玩意又不对了,然后搞了半天(主要是想降低tomcat版本,结果boot2.x降版本不好搞,会出问题),还是没搞对,我就放弃了。

但是,终究还是要搞的,我就只能按照书上的版本试,重新创建了一个项目(之前我是通过maven的父pom来继承的)降低springboot(1.5.22.RELEASE)和springcloud(Brixton.SR5)版本,再次引入zipkin的依赖,发现就算不写版本号都没啥问题了。

zipkin client 端:

注意我的zipkin client是继承的父pom文件,依赖的高版本的springboot和spring cloud

zipkin-client是按官方文档要求搞的:依赖的spring-cloud-starter-zipkin包,并进行正常配置。启动没有报错,但是在zipkin client向zipkin server自动发送数据请求时,报错了:大致就是说访问/api/v2/spans接口时报404。我分析一下报错原因:

zipkin server方面由于是没有指定zipkin版本号(跟着spring boot 的版本走的),所以它自动导入的版本是io.zipkin.java:zipkin:1.28.0(注意这个serer项目的springboot版本是低于2的)

zipkin client方面:由于我这个项目跟着父pom走的,所以spring boot版本是大于2的,这样导致了我在依赖的时候并不是像boot2以前的版本那样去依赖spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin包来激活zipkin,而是通过spring-cloud-starter-zipkin依赖包来激活zipkin的。

而spring-cloud-starter-zipkin这个包它依赖的zipkin的版本是:io.zipkin.zipkin2:zipkin:2.19.3,仔细观察下绿色的字的不同,会发现,zipkin server这个springboot2以前版本依赖的zipkin包相比于springboot2之后依赖的zipkin包少了个2,关键就在这里了。

io.zipkin.java:zipkin:1.28.0因为没有2所以我们简称v1版本,io.zipkin.zipkin2:zipkin:2.19.3我们简称v2版本。

导入v1版本的zipkin后,通过项目启动的mapping映射关系可以看出它里面的api都是/api/v1。。。

导入v2版本的zipkin可以发现,它里面的api是/api/v2。。。

所以结果就出来了,由于zipkin server依赖的zipkin和zipkin client依赖的zipkin 大版本是不一样的,所以就出现了404的问题。

解决:

宗旨就是让zipkin server和zipkin client的zipkin大版本要相同,要么都是io.zipkin.java,要么都是io.zipkin.zipkin2

如果你的项目是spring boot1.x  (<2):

  • 那么你就照我上面写的步骤去搞就行

如果你的项目是spring boot 2.x (>2):

  • zipkin server: 自己在本地搭建一个zipkin server,不使用java构建。类似redis,rabbitmq那样在本地安装即可。同时也要注意一下你下的zipkin安装包的版本,一定要是大于2的,例如2.x.x (springboot 在2.0后就不推荐创建springboot项目启动zipkin server了,推荐自己搭建zipkin server,因为你会发现spring boot 2.0 之后,你在引入依赖时必须要指定zipkin-server的版本号,而自己指定版本号会出现非常多的问题)
  • zipkin client:需要引入spring-cloud-starter-zipkin依赖包,而不是spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin了,然后其他正常操作即可。

不要试图通过降低zipkin client 端的spring-cloud-starter-sleuth和spring-cloud-sleuth-zipkin依赖包的版本来解决这个bug,因为这些我都试过了,不起作用,会出现其他bug

 

注意spring cloud版本要和spring boot版本对应,否则会出现很多问题:

springboot使用soket springboot sleuth_spring cloud