Sleuth 入门教程 - 中级篇

大纲

Spring Cloud 与 SkyWalking

SkyWalking 介绍

SkyWalking 的概述

SkyWalking 创建于 2015 年,用于提供分布式追踪功能。从 5.x 开始,项目进化为一个完成功能的 APM 系统,被用于追踪、监控和诊断分布式系统,特别是使用微服务架构、云原生或容器技术。更多介绍可参考:SkyWalking 官网SkyWalking GitHub 项目

SkyWalking 的功能

SkyWalking 提供的主要功能如下:

  • 分布式追踪和上下文传输
  • 应用、实例、服务性能指标分析
  • 根源分析
  • 应用拓扑分析
  • 应用和服务依赖分析
  • 慢服务检测
  • 性能优化

SkyWalking 的特性

SkyWalking 主要特性如下:

  • 多语言探针或者类库
    • Java 自动探针,追踪和监控程序时,不需要修改源码
    • 社区提供的语言探针:.NET Core、Node.js
  • 多种后端存储
    • 支持 Elastic Search、H2
    • 支持 OpenTrancing:Java 自动探针和 OpenTracing API 协同工作。
  • 轻量级、完善的后端聚合和分析功能
  • 现代化 WebUI
  • 日志集成
  • 应用、实例和服务的告警
  • 支持接受其他跟踪器的数据格式:
    • Zipkin JSON、Thrift、Protobuf v1 和 v2 格式,由 OpenZipkin 库提供支持
    • Jaeger 采用 Zipkin Thrift 或 JSON v1/v2 格式

SkyWalking 整体架构

SkyWalking 的整体架构主要由四部分组成 Collector、Agent、Web、Storage,具体如下图所示。从上到下是应用级别的接入,可以使用 SDK 形式的接入,也使用非入侵性的 Agent 形式接入,Agent 负载将数据转化成 SkyWalking Trace 数据协议,通过 HTTP 或者是 gRPC 发送到 Collector,然后 Collector 对收集到的数据进行分析和聚合,最后存储到 ElasticSearch 或者 H2 中,一般情况下 H2 只用于测试,右边的 UI 是通过 HTTP + GrahQL 进行数据获取展示,大体的流程就是这样。

SkyWalking 实战案例

本节将演示 Spring Cloud 与 SkyWalking 的实战案例,该案例使用了两个核心服务(Consumer Service、Provider Service)、Zuul 网关、Eureka 作为注册中心。案例的调用流程是这样的,首先通过访问 Zuul 网关,然后再将请求转发到 Consumer Service,接着 Consumer Service 通过 Feign 远程调用 Provider Service,最后返回响应结果。具体的调用流程图如下所示:

1. 版本说明

在本案例中,使用各组件的版本如下所示:

组件版本
Spring Boot2.0.3
Spring CloudFinchley.RELEASE
ElasticSearch5.6.10
SkyWalking5.0.0-GA

2. 安装与启动 SkyWalking

3. 创建 Maven 父级 Pom 工程

在父工程里面配置好工程需要的父级依赖,目的是为了更方便管理与简化配置,具体 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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>

<!-- 利用传递依赖,公共部分 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<!--注意:这里需要添加以下配置,否则可能会有各种依赖问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

4. 创建 Eureka 工程

创建 Eureka Server 的 Maven 工程,配置工程里的 pom.xml 文件,需要引入 spring-cloud-starter-netflix-eureka-server

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

创建 Eureka Server 的启动主类,这里添加相应注解,作为程序的入口:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

添加 Eureka Server 需要的 application.yml 配置文件到工程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8090

eureka:
instance:
hostname: 127.0.0.1
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
waitTimeInMsWhenSyncEmpty: 0
enableSelfPreservation: false

5. 创建 Zuul 工程

创建 Zuul 的 Maven 工程,配置工程里的 pom.xml 文件,需要引入 spring-cloud-starter-netflix-zuul。由于需要将服务注册到 Eureka Server,工程下的 pom.xml 文件还需要引入 spring-cloud-starter-netflix-eureka-client

1
2
3
4
5
6
7
8
9
10
<dependencies>
<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-zuul</artifactId>
</dependency>
</dependencies>

创建 Zuul 的启动主类,添加 @EnableDiscoveryClient@EnableZuulProxy 注解,作为程序的入口:

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

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

}

提示

在主启动类上面的 @EnableDiscoveryClient@EnableZuulProxy 注解,主要用于开启服务发现和 Zuul 代理转发的功能。

添加 Zuul 需要的 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
server:
port: 7070

spring:
application:
name: zuul-service

eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8090/eureka
instance:
prefer-ip-address: true

zuul:
routes:
service-consumer:
path: /client/**
serviceId: consumer-service

ribbon:
eureka:
enabled: true
ReadTimeout: 30000
ConnectTimeout: 30000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: false

hystrix:
threadpool:
default:
coreSize: 1000 # 并发执行的最大线程数,默认 10
maxQueueSize: 1000 # BlockingQueue 的最大队列数
queueSizeRejectionThreshold: 500 # 即使 maxQueueSize 没有达到,达到 queueSizeRejectionThreshold 该值后,请求也会被拒绝
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 120001

提示

在上述配置内容中,添加了 Ribbon 和 Hystrix 相关配置是为了在首次请求时不会出现超时现象。

6. 创建 Provider 工程

为了测试 Feign 的 Web 服务客户端的功能,必须要有一个服务提供者。创建 Provider 的 Maven 工程后,由于需要将服务注册到 Eureka Server,工程下的 pom.xml 文件需要引入 spring-cloud-starter-netflix-eureka-client

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>

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

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、注册中心地址与端口号:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 9090

spring:
application:
name: provider-service

eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8090/eureka
instance:
prefer-ip-address: true

创建用于测试的 Controller 类:

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

@RequestMapping(value = "/getSendInfo", method = RequestMethod.GET)
public String getSendInfo(@RequestParam("serviceName") String serviceName) {
return serviceName + " ---> " + "Provider-Service";
}

}

7. 创建 Consumer 工程

创建 Consumer 的 Maven 工程,配置工程里的 pom.xml 文件,需要引入 spring-cloud-starter-openfeign。由于需要从 Eureka Server 获取服务列表,即作为 Eureka 客户端,还需要引入 spring-cloud-starter-netflix-eureka-client

1
2
3
4
5
6
7
8
9
10
<dependencies>
<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-openfeign</artifactId>
</dependency>
</dependencies>

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

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

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

}

创建服务接口类,用于通过 Feign 调用 Provider 服务:

1
2
3
4
5
6
7
@FeignClient(name = "provider-service")
public interface SkyFeignService {

@RequestMapping(value = "/getSendInfo", method = RequestMethod.GET)
String getSendInfo(@RequestParam("serviceName") String serviceName);

}

创建用于测试的 Controller 类,使用多种方式调用 Provider 服务的那个自定义 API:

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

@Autowired
private SkyFeignService providerFeignService;

@RequestMapping(value = "/getInfo", method = RequestMethod.GET)
public String getInfo() {
return providerFeignService.getSendInfo("Consumer-Service");
}

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 6060

spring:
application:
name: consumer-service

eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8090/eureka
instance:
prefer-ip-address: true

8. 使用 Agent 启动服务和监控

官网 下载 SkyWalking 的 5.0.0-GA 版本,解压后得到里面的 agent 目录(如下),该目录是探针相关的。

1
2
3
4
5
6
7
8
9
10
11
apache-skywalking-apm-incubating
├── agent
├── bin
├── collector-libs
├── config
├── DISCLAIMER
├── LICENSE
├── licenses
├── NOTICE
├── README.txt
└── webapp

手动创建四个目录(如下),分别为 service-eureka、service-zuul、service-consumer、service-provider,主要用于存放被监控的 Jar 和 agent 目录,每个应用单独使用一个对应的 agent 目录进行启动。

1
2
3
4
5
6
7
8
9
10
11
12
├── service-consumer
│   ├── agent
│   └── consumer-service-1.0-SNAPSHOT.jar
├── service-eureka
│   ├── agent
│   └── eureka-service-1.0-SNAPSHOT.jar
├── service-provider
│   ├── agent
│   └── provider-service-1.0-SNAPSHOT.jar
└── service-zuul
├── agent
└── zuul-service-1.0-SNAPSHOT.jar

其中的 agent 目录是之前下载 SkyWalking 并解压后得到的,分别修改 agent/config 目录里面的 agent.config 文件,只需要修改 agent.application_ code 这一项配置,这个代表应用。每个配置文件对应改为:

1
2
3
4
agent.application_code=service-eureka
agent.application_code=service-zuul
agent.application_code=service-provider
agent.application_code=service-consumer

而服务应用是上面开发好的,通过 Maven 命令 maven package 将项目打包后得到可执行的 Jar。最后,通过下面的命令按顺序启动要监控的服务,具体如下:

1
2
3
4
5
6
7
java -javaagent:/usr/local/skywalking/service-eureka/agent/skywalking-agent.jar -jar /usr/local/skywalking/service-eureka/eureka-service-1.0-SNAPSHOT.jar

java -javaagent:/usr/local/skywalking/service-zuul/agent/skywalking-agent.jar -jar /usr/local/skywalking/service-zuul/zuul-service-1.0-SNAPSHOT.jar

java -javaagent:/usr/local/skywalking/service-provider/agent/skywalking-agent.jar -jar /usr/local/skywalking/service-provider/provider-service-1.0-SNAPSHOT.jar

java -javaagent:/usr/local/skywalking/service-consumer/agent/skywalking-agent.jar -jar /usr/local/skywalking/service-consumer/consumer-service-1.0-SNAPSHOT.jar

启动命令从上到下依次执行即可,所有应用启动完成后,通过 http://localhost:8080 可以访问 SkyWalking 的管理界面,默认登录的用户名 / 密码是 admin / admin

通过 Postman 等 HTTP 工具调用 http://127.0.0.1:7070/client/getInfo 接口后,此时观察 SkyWalking 首页最下面会出现一个调用过程:

更详细的调用信息可以切换顶部左侧菜单中的 Applicaiotn、Service、Topology、Trace 和 Alarm 等标签项,首先看一下 Application 展示的内容:

接下来看一下 Service 展示的内容:

接下来看一下 Toplogy 展示的内容(整个拓扑图):

提示

  • 刷新 Toplogy 页面时,可能会先看到一个 service-eureka 的节点。重复刷新可能会出现 serivce-consumer 指向 service-provider 的请求,再请求一次,再刷新可能会出现 user --> service-zuul --> service-consumer --> service-provider 的调用,为什么会出现这样的现象呢?
  • 这里出现延迟的原因和请求调用顺序有关系,请求调用是顺序开始,但完成却是倒序完成的。在每个应用中完成请求处理后,会发送监控信息到 Collector,然后 Collector 才能依次输出采集的结果,所以就出现这种延迟显示的情况。

9. 下载案例代码

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

Spring Cloud 与 Pinpoint

Pinpoint 介绍

Pinpoint 的概述

Pinpoint 是一个分析大规模分布式系统的平台,并提供处理大量跟踪数据的解决方案。它自 2012 年 7 月开发,并千 2015 年 1 月 9 日作为开源项目推出。Pinpoint 是由韩国人编写的著名的 APM (Application Performance Management) 系统。

Pinpoint 的特性

  • Pinpoint 的特点

    • 分布式事务跟踪,跟踪跨分布式应用的消息。
    • 自动检测应用拓扑,帮助开发者搞清楚应用的架构。
    • 水平扩展以便支持大规模服务器集群。
    • 提供代码级别的可见性以便轻松定位失败点和瓶颈。
    • 使用字节码增强技术,添加新功能而无须修改代码。
  • Pinpoint 的优势

    • 非入侵式:使用字节码增强技术,添加新功能而无须修改代码。
    • 资源消耗:对性能的影响最小(资源使用量增加)

Pinpoint 的兼容性

下面对 Pinpoint 涉及的组件和存储方面的中间件进行版本对比。

Pinpoint 的核心模块

Pinpoint 主要包含以下 4 个架构模块:

  • HBase(用于存储数据)。
  • PinpointCollector(部署在 Web 容器上)。
  • Pinpoint Web(部署在 Web 容器上)。
  • Pinpoint Agent(附加到需要分析的 Java 应用程序)。

为了更好地说明 Pinpoint 中架构的模块,需要看详细的架构图(如下)。大体的流程是这样的:首先通过 Agent 收集调用应用的数据,将数据发送给 Collector,Collector 负责处理和分析数据,最后将数据存储到 HBase 中,可以通过 Pinpoint Web UI 查看已经分析好的调用分析数据。

Pinpoint 的数据结构

在 Pinpoint 中,数据结构的核心由 Span、Trace 和 TraceId 组成。

  • Span:RPC(远程过程调用)跟踪的基本单位。它表示 RPC 到达时处理的工作,包含跟踪数据。为确保代码级别的可见性,Span 将子项标记为 SpanEvent,作为数据结构。每个 Span 包含一个 TraceId。
  • Trace:一系列跨度;它由相关的 RPC(Spans)组成。同一个跟踪中的跨距共享相同的 TransactionId。Trace 通过 SpanIds 和 ParentSpanIds 排序为分层树状结构。
  • TraceId:由 TransactionId、SpanId 和 ParentSpanId 组成的密钥集合。TransactionId 表示消息 ID,SpanId 和 ParentSpanId 都表示 RPC 的父子调用关系。
    • TransactionId(TxId):来自单个事务的分布式系统发送 / 接收的消息的 ID;它必须在整个服务器组中是全局唯一的。
    • SpanId:接收 RPC 消息时处理的作业的 ID;它是在 RPC 到达服务器节点时生成的。
    • ParentSpanId(pSpanId):生成 RPC 的父 Span 的 SpanId。如果节点是事务的起始点,则不会有父跨度(对于这些情况,使用值 -1 来表示跨度是事务的根跨度。

提示

上图能够比较直观地说明这些 ID 结构直接的关系,同时可以很好地描述一个 Trace 的过程。

Pinpoint 与 Zipkin 对比

对比点 ZipkinPinpoint 说明
技术点Zipkin 依赖于 Spring 框架的 API 支持,监控内容和范围有限
Pinpoint 使用字节码注入技术,监控更为深入和广泛,比如可以看到调用了几次 Redis、哪几个微服务、MySQL 以及执行的 SQL 等,可以非常方便地追踪问题
接入成本Zipkin 需要对应用进行简单改造,包括 Sleuth 的引入和配置
Pinpoint 只需要在服务器打入探针,对业务没有任何改动
扩展性Zipkin 使用了 Spring Cloud 常用的 RESTful 协议,可以方便地进行扩展
Pinpoint 现在使用 gRPC 协议,也可以使用 Thrift 传输协议,出于传输数据量和性能的考虑
兼容性Zipkin 对 Spring Cloud 的兼容性更好,也得益于上面说的扩展性
产品相比较而言,Pinpoint 可以说是一款更成熟的产品
性能由于采集的数据范围不同,即使 Pinpoint 采用了高性能的传输协议,但是 Zipkin 的性能依然更高

Pinpoint 实战案例

由于篇幅有限,详细的实战案例代码可参考《重新定义 Spring Cloud 实战》的 第 16.7 小节(389 页)。

参考资料