Spring Cloud之微服务链路跟踪Sleuth

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Spring Cloud之微服务链路跟踪Sleuth,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、Sleuth

Spring Cloud Sleuth微服务跟踪Sleuth是Spring Cloud体系下一个模块工具,Sleuth采用的是Google的开源项目Dapper链路追踪组件的专业术语。

Sleuth用于在整个分布式系统中跟踪一个用户请求的过程(包括数据采集,数据传输,数据存储,数据分析,数据可视化)。捕获这些跟踪数据,就能构建微服务的整个调用链的视图,这是调试和监控微服务的关键工具。

在一个分布式系统中,会有数量众多的服务单元,而内部服务单元间的调用复杂性,决定了出现问题难以定位,所以出现了分布式链路追踪。

核心特点

特点 说明
提供链路追踪 通过sleuth可以很清楚的看出一个请求经过了哪些服务, 可以方便的理清服务间的调用关系
性能分析 通过sleuth可以很方便的看到每个请求的耗时,分析出哪些服务调用比较耗时,当服务调用耗时随着请求量的增大而增大时,也可以对服务的扩容提供一定的提醒作用
数据分析优化链路 对于频繁地调用一个服务,或者并行地调用等, 可以针对业务做一些优化措施
可视化 对于程序未捕获的异常,可以在zipkpin界面上看到

Trace

请求一个服务的API接口,需要调用多个服务,调用每个服务都会产生一个新的Span,所有由这个请求产生的Span组成这个Trace。即所有服务组成的调用链就像一个树结构,追踪这个调用链路得到的这个树结构可以称之为Trace。

Span

在一次Trace中,每个服务的每一次调用,就是一个基本工作单元,称之为Span,Span是一个64位ID唯一标识的。每个Span都有一个id作为唯一标识,同样每一次Trace都会生成一个traceId在Span中作为追踪标识,另外再通过一个parentId标明本次调用的发起者。Span还有其他数据信息,如:名称、节点上下文、时间戳、Span的ID等等

请求头核心参数

Sleuth会在每个请求的header上添加跟踪需求的重要信息

X-B3-TraceId:对应TraceID

X-B3-SpanId:对应SpanID

X-B3-ParentSpanId:前面一环的SpanID

X-B3-Sampled:是否被选中抽样输出

X-Span-Name:工作单元名称

通过请求头获取相应的信息

TraceID: request.getHeader("X-B3-TraceId")

SpanID : request.getHeader("X-B3-SpanId")

抽样收集

默认情况下,Sleuth以请求百分比的方式配置和收集跟踪信息,默认值0.1,代表收集10%的请求跟踪信息,可以通过配置spring.sleuth.sampler来修改收集的百分比。

二、示例Demo

添加依赖

创建父工程,添加如下依赖

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

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

创建4个子工程

创建工程1

@SpringBootApplication
public class Application {

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

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

创建API接口,添加服务间API调用

@RestController
public class TestSleuth {

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/app1")
    @ResponseBody
    public String test(HttpServletRequest request) {
        System.out.println("TraceId: " + request.getHeader("X-B3-TraceId"));
        System.out.println("SpanId: " + request.getHeader("X-B3-SpanId"));
        return restTemplate.getForEntity("http://localhost:8002/app2", String.class).getBody();
    }
}

配置application.yml

server:
  port: 8001
spring:
  application:
     name: app1
  sleuth:
     sampler:
        probability: 1     #收集比例,1表示100% ,全部收集

创建工程2

    @GetMapping("/app2")
    @ResponseBody
    public String test(HttpServletRequest request) {
        System.out.println("TraceId: " + request.getHeader("X-B3-TraceId"));
        System.out.println("SpanId: " + request.getHeader("X-B3-SpanId"));
        String app3 = restTemplate.getForEntity("http://localhost:8003/app3", String.class).getBody();
        String app4 = restTemplate.getForEntity("http://localhost:8004/app4", String.class).getBody();
        return "ReturnResult: " + app3 + " + " + app4;
    }
server:
  port: 8082
spring:
  application:
     name: app2
  sleuth:
     sampler:
        probability: 1 

创建工程3

  @GetMapping("/app3")
    @ResponseBody
    public String test(HttpServletRequest request) {
        System.out.println("TraceId: "+request.getHeader("X-B3-TraceId"));
        System.out.println("SpanId: "+ request.getHeader("X-B3-SpanId"));
        return "app3";
    }
server:
  port: 8003
spring:
  application:
     name: app3
  sleuth:
     sampler:
        probability: 1 

创建工程4

    @GetMapping("/app4")
    @ResponseBody
    public String test(HttpServletRequest request) {
        System.out.println("TraceId: " + request.getHeader("X-B3-TraceId"));
        System.out.println("SpanId: " + request.getHeader("X-B3-SpanId"));
        return "app4";
    }
server:
  port: 8004
spring:
  application:
     name: app4
  sleuth:
     sampler:
        probability: 1 

启动4个服务,访问http://localhost:8001/app1
在这里插入图片描述

TraceId: null
SpanId: null

TraceId: 03864c40c5590b82
SpanId: 550f00b71671657e

TraceId: 03864c40c5590b82
SpanId: 45fd8bf67e2c03c6

TraceId: 03864c40c5590b82
SpanId: 754789c6896ac0fb

三、Sleuth+Zipkin监控数据

Sleuth提供了链路追踪基础,并没有收集各个服务上请求链路的跟踪数据。

Zipkin是一款开源的分布式实时数据追踪系统,主要功能是聚集来自各个异构系统的实时监控数据,和微服务架构下的接口直接的调用链路和系统延时问题。

GitHub: https://github.com/openzipkin/zipkin

Zipkin主要由4个核心组件构成:

Collector:接收各service传输的数据

Storage:存储收集过来的数据,当前支持Cassandra,MySQL,Elasticsearch,默认存储在内存中

API(Query): 负责查询Storage中存储的数据,提供简单的JSON API获取数据,主要提供给web UI使用

Web UI:提供简单的web界面

下载Zipkin

方式一: https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec

方式二: curl -sSL https://zipkin.io/quickstart.sh | bash -s

启动Zipkin

Docker 方式

docker  pull openzipkin/zipkin

docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin

Jar 包方式

java -jar zipkin-server-2.23.4-exec.jar
                  oo
                 oooo
                oooooo
               oooooooo
              oooooooooo
             oooooooooooo
           ooooooo  ooooooo
          oooooo     ooooooo
         oooooo       ooooooo
        oooooo   o  o   oooooo
       oooooo   oo  oo   oooooo
     ooooooo  oooo  oooo  ooooooo
    oooooo   ooooo  ooooo  ooooooo
   oooooo   oooooo  oooooo  ooooooo
  oooooooo      oo  oo      oooooooo
  ooooooooooooo oo  oo ooooooooooooo
      oooooooooooo  oooooooooooo
          oooooooo  oooooooo
              oooo  oooo

     ________ ____  _  _____ _   _
    |__  /_ _|  _ \| |/ /_ _| \ | |
      / / | || |_) | ' / | ||  \| |
     / /_ | ||  __/| . \ | || |\  |
    |____|___|_|   |_|\_\___|_| \_|

:: version 2.23.4 :: commit 3827477 ::

访问Zipkin

访问:IP:9411
在这里插入图片描述

添加依赖

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

添加zipkin配置

在每一个工程配置文件中添加zipkin的地址配置

spring:
  sleuth:
     sampler:
        probability: 1
  zipkin:
      base-url: http://IP:9411

查看追踪的日志

重新启动所有工程,再次访问http://localhost:8001/app1,然后访问http:/IP:9411可以查看到追踪的日志

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、Zipkin+MySQL数据存储

zipkin默认将收集到的数据存储在内存中,同时Zipkin支持多种存储介质,如将其存储在MySQL数据库中。

创建相应数据库及表

建表脚本文件:https://github.com/openzipkin/zipkin/blob/master/zipkin-storage/mysql-v1/src/main/resources/mysql.sql

CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `remote_service_name` VARCHAR(255),
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
  PRIMARY KEY (`trace_id_high`, `trace_id`, `id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;


CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT,
  `error_count` BIGINT,
  PRIMARY KEY (`day`, `parent`, `child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

重启zipkin

zipkin相关配置参考:https://github.com/openzipkin/zipkin/blob/master/zipkin-server/src/main/resources/zipkin-server-shared.yml

java -DSTORAGE_TYPE=mysql -DMYSQL_USER=root -DMYSQL_PASS=123456 -DMYSQL_HOST=127.0.0.1
 -DMYSQL_TCP_PORT=3306 -DMYSQL_DB=zipkin -jar zipkin-server-2.23.4-exec.jar

在这里插入图片描述

在这里插入图片描述

五、Zipkin+RabbitMQ

调用链路过程中出现的网络中断或其他原因就会造成服务无法追踪导致问题无法排查。

引入使用RabbitMQ作为链路跟踪的消息中间件,以此进行信息的收集,避免上述情况的产生。

服务将链路追踪数据发送到MQ,Zipkin服务再从MQ消费,从而对系统解耦,提升性能

docker安装RabbitMQ: https://blog.csdn.net/qq_38628046/article/details/106875278

重启Zipkin

添加相应启动参数,重启Zipkin

java -DSTORAGE_TYPE=mysql -DMYSQL_USER=root -DMYSQL_PASS=123456 -DMYSQL_HOST=127.0.0.1 
-DMYSQL_TCP_PORT=3306 -DMYSQL_DB=zipkin -DRABBIT_ADDRESSES=IP:5672 
-DRABBIT_USER=guest -DRABBIT_PASSWORD=guest -jar zipkin-server-2.23.4-exec.jar

添加依赖

<!--Spring Cloud Sleuth + Zipkin + RabbitMQ 整合-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>

添加zipkin配置

Sleuth提供了多种方式将追踪数据传输给zipkin,Sleuth默认采用Http方式将追踪数据传输给Zipkin

@ConfigurationProperties("spring.zipkin.sender")
public class ZipkinSenderProperties {

	/**
	 * Means of sending spans to Zipkin.
	 */
	private SenderType type;

	public SenderType getType() {
		return this.type;
	}
	public void setType(SenderType type) {
		this.type = type;
	}
	/**
	 * Types of a sender.
	 */
	public enum SenderType {
		/**
		 * ActiveMQ sender.
		 */
		ACTIVEMQ,
		/**
		 * RabbitMQ sender.
		 */
		RABBIT,
		/**
		 * Kafka sender.
		 */
		KAFKA,
		/**
		 * HTTP based sender.
		 */
		WEB
	}
}

在每一个工程配置文件中添加rabbitmq的地址配置

spring:
  sleuth:
    sampler:
      probability: 1
  zipkin:
    base-url: http://localhost:9411
    sender:
      type: rabbit
  rabbitmq:
    host: ip
    port: 5672
    username: guest
    password: guest

测试

重启工程服务,访问http://localhost:8001/app1,再访问http://localhost:9411查看追踪的日志
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137023.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!