Micrometer 入门教程 - 基础篇

大纲

前言

Sleuth 停更说明

Spring Cloud 官方已经宣布将 Sleuth 项目迁移至 Micrometer 项目,也就是以后不会再为 Sleuth 项目提供更新与维护。

Spring Cloud Sleuth 的最后一个版本是 2024 年 2 月 9 日发布的 3.1.11,而且 Sleuth 支持的 Spring Boot 的最后一个主要版本是 2.x,不再支持 Spring Boot 3.x 及以上版本。

分布式系统面临的问题

在分布式与微服务场景下,通常需要解决以下问题:

  • 在大规模分布式与微服务集群下,如何实时观测系统的整体调用链路情况?
  • 在大规模分布式与微服务集群下,如何快速发现并定位到问题?
  • 在大规模分布式与微服务集群下,如何尽可能精确的判断故障对系统的影响范围与影响程度?
  • 在大规模分布式与微服务集群下,如何尽可能精确的梳理出服务之间的依赖关系,并判断出服务之间的依赖关系是否合理?
  • 在大规模分布式与微服务集群下,如何尽可能精确的分析整个系统调用链路的性能与瓶颈点?
  • 在大规模分布式与微服务集群下,如何尽可能精确的分析系统的存储瓶颈与容量规划?

分布式链路追踪介绍

分布式链路追踪的简介

在分布式系统(特别是微服务架构)中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。这就是 ** 分布式链路追踪(Distributed Tracing)需要解决的问题,也就是将一次分布式请求还原成调用链路,进行日志记录,性能监控并将一次分布式请求的调用情况集中展示。** 比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等。

分布式链路追踪的作用

  • 故障定位

    • 当系统发生故障时,分布式链路追踪可以帮助快速确定故障发生的具体服务或组件。通过查看请求在不同服务中的延迟情况,可以找出导致性能瓶颈的具体服务。
  • 性能分析

    • 分布式链路追踪可以帮助识别请求在系统中的响应时间,开发者可以根据每个服务的延迟情况优化系统的性能。
  • 请求上下文跟踪

    • 在分布式系统中,服务之间的调用是异步和分散的,链路追踪可以帮助开发者全面了解每个请求的完整执行流程,甚至可以跟踪某些异步任务或消息队列中的任务执行情况。
  • 提升可观测性

    • 分布式链路追踪与日志、度量(Metrics)一起,构成了完整的系统可观测性工具链。这三者结合使用,可以为开发者提供从系统健康状况到具体请求细节的全面视图。

分布式链路追踪的挑战

尽管分布式链路追踪带来了许多好处,但它也面临一些挑战:

  • 性能开销

    • 追踪每个请求的所有跨服务操作需要记录大量的数据,可能会带来额外的性能开销,尤其是在高并发场景下。
  • 数据存储和分析

    • 由于分布式系统中存在大量的请求和调用,追踪系统需要有效地存储和分析这些数据,才能为开发者提供有用的信息。
  • 异构系统的支持

    • 分布式系统可能使用不同的技术栈和通信协议,链路追踪工具需要具备对这些异构系统的广泛支持。

分布式链路追踪的核心概念

  • Trace(链路)

    • Trace 是指一个请求在分布式系统中整个执行路径的记录。它包含了请求从接收到响应的整个过程,跨越了多个服务、进程或线程。
    • 每个 Trace 代表一次完整的请求生命周期,可以帮助理解请求是如何流动的,以及在哪些地方可能发生了延迟或错误。
  • Span(跨度)

    • Span 是分布式追踪系统中的基本操作单元,代表一个请求在单个服务或组件中的一次操作。每个 Span 包含了以下信息:
      • 开始时间和结束时间
      • 服务名称或操作名称
      • 执行时间(Duration)
      • 上下文(如请求 URL、状态码等)
    • 每个 Trace 通常由多个 Span 组成,Span 之间可以是父子关系。一个父 Span 代表一个请求在当前服务的处理,子 Span 代表请求在调用的下游服务中的处理。
  • 上下文传播

    • 在分布式系统中,请求在服务之间传递时,需要将 Trace 和 Span 的上下文信息随请求一起传递,这称为上下文传播。
    • 常见的上下文传播方式包括 HTTP Headers(如 traceparent)或 gRPC 中的元数据。这个过程确保每个服务都能识别同一个请求的 Trace ID 和 Span ID。
  • Trace ID 和 Span ID

    • Trace ID 是一个唯一标识符,用来标识某个请求的完整追踪链路。所有涉及到该请求的服务都共享相同的 Trace ID。
    • Span ID 是每个 Span 的唯一标识符,用于标识该请求在某个服务或组件中的特定操作。每个 Span 都有自己的 Span ID。
  • 术语

    • CS(Client Sent): 客户端发送请求的时间
    • SR(Server Received): 服务端接收到请求的时间
    • CR(Client Received): 客户端接收到响应的时间
    • SS(Server Sent): 服务端发送响应的时间
    • SR - CS = 网络传输时间(请求发送)
    • SS - SR = 业务处理时间
    • CR - CS = 远程调用耗时
    • CR - SS = 网络传输时间(请求响应)

分布式链路追踪的工作原理

分布式链路追踪通过以下步骤实现对请求的全程跟踪:

  • (1) 请求接收:当一个请求到达系统(如通过 API 网关),系统会创建一个新的 Trace ID 和 Root Span,并开始记录请求的处理信息。
  • (2) 跨服务调用:当该请求调用其他服务时,上下文信息(Trace ID 和 Span ID)会随着请求一起传递到下游服务。下游服务会根据上下文信息创建子 Span,记录该服务处理该请求的相关信息。
  • (3) 数据收集:每个服务的 Span 数据会被记录下来,并在 Span 完成后发送到追踪后端(如 Jaeger、Zipkin、OpenTelemetry、SkyWalking 等)。
  • (4) Trace 汇总:在追踪后端,所有 Span 数据会根据 Trace ID 进行关联,构建出完整的请求调用链。
  • (5) 展示和分析:开发者可以通过可视化工具查看每个请求的详细执行路径,了解每个服务的执行时间、请求状态、错误等信息。这有助于分析系统瓶颈、性能问题以及错误排查。

假定三个微服务的调用链路如下图所示,Service 1 调用 Service 2,Service 2 调用 Service 3 和 Service 4。

那么每一条链路追踪都会在每个服务调用的时候加上 Trace ID 和 Span ID,而且每一条链路都是通过 Trace Id 唯一标识、Span 标识发起的请求信息,各个 Span 之间通过 Parent ID 关联起来。在一次业务处理中,Trace 和 Span 的详细流转情况如下图所示:

各个 Span 之间的父子关系图如下所示:

分布式链路追踪的技术选型

技术选型总结

  • 如果是中小型项目,且已使用 Spring Cloud 生态技术,建议采用 Sleuth(Micrometer) + Zipkin 方案。
  • 如果是大型分布式系统,尤其是要求在高性能、高并发的场景下表现优异,建议采用 Pinpoint、SkyWalking 或者 OpenTelemetry。
  • 更多关于分布式链路追踪的技术选型介绍,可以参考 这里

Zipkin 使用

Zipkin 的介绍

Zipkin 是一款开源的分布式实时数据追踪系统(Distributed Tracking System),基于 Google Dapper 的论文设计而来,由 Twitter 公司开源贡献。Zipkin 能够收集微服务运行过程中的实时调用链路信息,并能够将这些调用链路信息展示到 Web 图形化界面上供开发人员分析。开发人员能够从 ZipKin 中分析出调用链路中的性能瓶颈,识别出存在问题的应用程序,进而定位问题和解决问题。

思考

  • 单有 Sleuth(Micrometer)行不行?
  • Zipkin 为什么会出现?

当没有配置 Sleuth(Micrometer) 链路追踪的时候,INFO 日志信息里面是 [passjava-question,,,],后面跟着三个空字符串,如下图所示:

当配置了 Sleuth(Micrometer)链路追踪的时候,追踪到的日志信息是 [passjava-question,504a5360ca906016,e55ff064b3941956,false],第一个是 Trace ID,第二个是 Span ID。但是,这样只有日志信息而没有图表,观看起来不方便,引入图形化的 Zipkin 链路监控就有助于分析了。

提示

  • Zipkin 支持将追踪数据存储到 Cassandra、Elasticsearch、MySQL。
  • Zipkin 支持多种数据采集方式,包括从 Kafka、RabbitMQ、ActiveMQ 采集追踪数据。

Zipkin 的安装

Brave 使用

Brave 的介绍

Brave 是用来装备 Java 程序的类库,提供了面向 Standard Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等接口的装备能力,可以通过编写简单的配置和代码,让基于这些框架构建的应用可以向 Zipkin 报告数据。同时 Brave 也提供了非常简单且标准化的接口,在以上封装无法满足要求的时候可以方便扩展与定制。Brave 利用 Reporter 向 Zipkin 的 Collector 发送追踪数据,如下图所示:

Brave 主要是利用拦截器在请求前和请求后分别埋点。例如 SpingMVC 监控使用 Interceptors,而 MySQL 监控使用 StatementInterceptors。同理 Dubbo 的监控是利用 com.alibaba.dubbo.rpc.Filter 来过滤生产者和消费者的请求。

Micrometer 使用

Micrometer 的介绍

Spring Cloud Micrometer 是一个用于监控和度量 Spring Cloud 应用程序性能的工具。它通过 Micrometer 库集成了各种监控系统(如 Prometheus、Datadog 等),可以帮助开发者收集和分析应用程序的运行数据,比如请求数、响应时间、CPU 使用率等。更多介绍可参考:Micrometer GitHub 项目Micrometer 官方文档(新版)Micrometer 官方文档(旧版)

Micrometer 的功能

Spring Cloud Micrometer 提供了一套分布式链路追踪(Distributed Tracing)解决方案,且兼容支持 Zipkin 展现。

  • 自动收集指标:Spring Cloud Micrometer 自动生成应用程序的性能指标,无需手动编写代码。
  • 多监控系统支持:它支持将数据发送到多个监控平台,让开发者可以根据需要选择合适的工具。
  • 轻量化:Micrometer 对应用性能影响很小,可以在不显著增加负载的情况下进行监控。
  • 灵活的定制化:开发者可以根据需求定制和扩展监控指标。

Micrometer 的使用案例

版本使用说明

在本文的所有案例中,各个组件统一使用以下版本:

组件版本说明
Spring Boot3.2.0
Spring Cloud2023.0.0
Consul1.15.4 作为注册中心
Zipkin3.4.1 作为分布式链路追踪系统,负责存储追踪数据和图形化展示
Micrometer Tracing1.2.0 负责追踪数据的采样

创建父级 Pom 工程

由于 Micrometer 只是一个门面工具,自身并没有实现完整的链路追踪系统,具体的链路追踪需要另外引入第三方链路追踪系统的依赖来实现。使用 Micrometer + Brave + Zipkin 实现分布式链路追踪所需的核心组件如下所示:

组件名称组件说明
micrometer-tracing-bom 用于导入指标追踪的版本中心
micrometer-tracing 用于指标追踪
micrometer-tracing-bridge-brave 一个 Micrometer 模块,用于与分布式跟踪工具 Brave 集成,以收集应用程序的分布式追踪数据。Brave 是一个开源的分布式跟踪工具,它可以帮助用户在分布式系统中跟踪请求的流转,它使用一种称为 “跟踪上下文” 的机制,将请求的跟踪信息存储在请求的头部,然后将请求传递给下一个服务。在整个请求链中,Brave 会将每个服务处理请求的时间和其他信息存储到追踪数据中,以便用户可以了解整个请求的路径和性能。
micrometer-observation 一个基于度量库 Micrometer 的观测模块,用于收集应用程序的度量数据。
feign-micrometer 一个 Feign HTTP 客户端的 Micrometer 模块,用于收集客户端请求的度量数据。
zipkin-reporter-brave 一个用于将 Brave 追踪数据报告到 Zipkin 跟踪系统的库。

补充说明

若需要监控和管理 SpringBoot 应用程序,比如监控各个微服务的运行状态、CPU 使用率、JVM 堆内存使用状况等,可以额外引入 spring-boot-starter-actuator 模块,该模块可配合 Spring Cloud Admin 或者 Prometheus 一起使用,以此达到微服务应用可视化监控的目的。

在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 Maven 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.22</hutool.version>
<lombok.version>1.18.26</lombok.version>
<spring.boot.version>3.2.0</spring.boot.version>
<spring.boot.test.version>3.1.5</spring.boot.test.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
<micrometer-tracing.version>1.2.0</micrometer-tracing.version>
<micrometer-observation.version>1.12.0</micrometer-observation.version>
<feign-micrometer.version>12.5</feign-micrometer.version>
<zipkin-reporter-brave.version>2.17.0</zipkin-reporter-brave.version>
</properties>

<dependencyManagement>
<dependencies>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Micrometer Tracing-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bom</artifactId>
<version>${micrometer-tracing.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Micrometer 指标追踪 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--Micrometer 适配 Zipkin 的桥接包 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometer-tracing.version}</version>
</dependency>
<!--Micrometer Observation-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
<version>${micrometer-observation.version}</version>
</dependency>
<!--Feign Micrometer-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
<version>${feign-micrometer.version}</version>
</dependency>
<!--Zipkin Reporter Brave-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
<version>${zipkin-reporter-brave.version}</version>
</dependency>
<!--HuTool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!--SpringBoot Test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

创建 Provider 工程

为了测试 Micrometer + Zipkin 的使用效果,需要创建一个服务提供者。这里创建 Provider 的 Maven 工程,由于需要将服务注册到 Consul,工程下的 pom.xml 文件需要引入 spring-cloud-starter-consul-discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!--Consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!--Micrometer 指标追踪 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>

<!--Micrometer 适配 Zipkin 的桥接包 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!--Micrometer Observation-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>

<!--Feign Micrometer-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>

<!--Zipkin Reporter Brave-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>

创建 Provider 的启动主类,添加注解 @EnableDiscoveryClient,将服务注册到 Consul

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {

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

}

application.yml 文件中指定服务名称(provider-service)、注册中心地址与端口号、Zipkin 的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 8001

spring:
application:
name: provider-service
cloud:
# Consul
consul:
host: 127.0.0.1
port: 8500
# 注册中心
discovery:
service-name: ${spring.application.name}
heartbeat:
enabled: true

management:
# Zipkin
zipkin:
tracing:
endpoint: http://127.0.0.1:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 # 采样率默认为 0.1(这里的 0.1 表示 10 次只能有一次被记录下来),值越大收集越实时

创建用于测试的 Controller 类

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class ProviderController {

/**
* 该接口用于测试 Micrometer 链路监控
*/
@GetMapping ("/provider/micrometer/{id}")
public String micrometer (@PathVariable ("id") Integer id) {
return "Hello, welcome to Micrometer, inputId :" + id + "\t" + UUID.fastUUID ();
}

}

创建 Consumer 工程

为了测试 Micrometer + Zipkin 的使用效果,需要创建一个服务调用者。这里创建 Consumer 的 Maven 工程,由于需要从 Consul 获取服务列表,即作为 Consul 的客户端,还需要引入 spring-cloud-starter-consul-discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!--Consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!--Micrometer 指标追踪 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>

<!--Micrometer 适配 Zipkin 的桥接包 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<!--Micrometer Observation-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>

<!--Feign Micrometer-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>

<!--Zipkin Reporter Brave-->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>

创建服务接口类,通过 OpenFeign 调用 Provider 提供的服务

1
2
3
4
5
6
7
8
9
10
@FeignClient ("provider-service")
public interface ProviderFeignApi {

/**
* 该接口用于测试 Micrometer 链路监控
*/
@GetMapping ("/provider/micrometer/{id}")
String micrometer (@PathVariable ("id") Integer id);

}

创建启动主类,添加 @EnableDiscoveryClient@EnableFeignClients 注解

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {

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

}

application.yml 文件中配置端口号、注册中心地址、断路器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 8003

spring:
application:
name: consumer-service
cloud:
# Consul
consul:
host: 127.0.0.1
port: 8500
# 注册中心
discovery:
service-name: ${spring.application.name}
heartbeat:
enabled: true

management:
# Zipkin
zipkin:
tracing:
endpoint: http://127.0.0.1:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 # 采样率默认为 0.1(这里的 0.1 表示 10 次只能有一次被记录下来),值越大收集越实时

创建用于测试的 Controller 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class ConsumerController {

@Autowired
private ProviderFeignApi providerFeignApi;

/**
* 该接口用于测试 Micrometer 链路监控
*/
@GetMapping ("/consumer/micrometer/{id}")
String micrometer (@PathVariable ("id") Integer id) {
return providerFeignApi.micrometer (id);
}

}

测试案例代码

  • (1) 首先启动 Zipkin 分布式跟踪系统,然后再启动 SpringBoot 应用

  • (2) 浏览器多次访问 http://localhost:8003/consumer/micrometer/33 接口,这是为了生成请求的追踪数据

  • (3) 浏览器通过 http://127.0.0.1:9411/zipkin/ 访问 Zipkin 的控制台页面,然后点击页面上的 RUN QUERY 查询按钮,这样就可以显示最近的请求列表

  • (4) 在 Zipkin 的控制台页面上,点击某一条请求数据右侧的 SHOW 按钮,这样就可以显示该请求的详细调用过程

  • (5) 在 Zipkin 的控制台页面上,点击导航栏的 Dependencies 选项,还可以显示服务调用之间的依赖关系

下载案例代码

  • 完整的案例代码可以从 这里 下载得到。

技术资源推荐