上一篇中,我们介绍了使用 Spring Cloud Gateway 对服务入口进行统一管理。这一篇中,我们来介绍一下微服务的链路追踪。在一个大型的微服务系统当中,服务与服务之间的调用是错综复杂的,调用链路往往也是非常冗长的,当服务出现异常时,如果我们要一个个服务去查看日志定位异常的话,是相当麻烦的。为此,我们可以使用 SkyWalking 对微服务调用链路进行追踪,快速定位问题。同时,通过 SkyWalking,我们可以更清晰的观测分布式系统。
1. 关于 SkyWalking
SkyWalking 是一个现代化的应用程序性能监控(Application Performance Monitoring ,简称“APM”)系统。逻辑上分为四个部分:探测器、平台后端、存储和UI。以下是 SkyWalking 官网的架构图。
下面是用通俗的方式简化架构图。
关于 SkyWalking 更多详细的介绍可以参考以下网址。
官网:https://skywalkinng.apache.org/
文档:https://skywalking.apache.org/docs/main/v9.6.0/readme
中文文档:https://skyapm.github.io/document-cn-translation-of-skywalking
2. 版本选择
虽然 SkyWalking 既不属于 Spring Cloud 体系,也不属于 Spring Cloud Alibaba 体系,但是
Java Agent 运行的 JDK 版本还是要考虑 Spring Cloud Alibaba 中 Spring Boot 的版本,因为 Java Agent 和 Spring Cloud Alibaba 中的微服务是运行在同一个 JDK 中的。由于我们的 Spring Cloud Alibaba 版本需要 Spring Boot 3.0.x 的版本,最低支持 JDK 17,因此 Java Agent 也要选择支持 JDK 17 的版本。当前官网提供的最新的几个下载版本为 8.14.0 - 9.0.0,支持 JDK 8 - 17,我们就选择 8.16.0 版本。至于 SkyWalking Servers(包含 oapService 和 webappService) 的版本,只需保证部署的版本与 Java Agent 之间服务的正常交互即可。
需要注意的是,SkyWalking Servers 当前官网提供的最新的几个下载版本为 9.0.0 - 9.6.0 版本,这些版本在 Linux 环境下均能正常启动和运行。但在 Windows 环境下, 9.4.0 - 9.6.0 版本一执行脚本,命令行窗口就闪退,闪退后连错误日志都不输出。 在这里,SkyWalking Servers 我们选择 9.2.0 的版本。
3. 安装
这里的安装,需要先安装 SkyWalking Servers ,然后再安装 Java Agent 。SkyWalking Servers 的安装包括 oapService 和 webappService 两部分;Java Agent 的安装,是在微服务 -jar 启动参数前,添加启动参数即可(例如:-javaagent:/path/to/skywalking-agent/skywalking-agent.jar)。
3.1. SkyWalking Servers
3.1.1. 文件下载
从官网下载 SkyWalking APM v9.2.0 版本(链接地址),下载 apache-skywalking-apm-9.2.0.tar.gz 包,解压后文件目录如下。
3.1.2. 目录说明
下面对 apache-skywalking-apm-9.2.0.tar.gz 解压后的主要目录进行说明。
3.1.2.1. bin 目录
bin 目录中,其中 *.bat 文件是 Windows 环境下的操作脚本,*.sh 文件是在 Linux 环境下的操作脚本。oapService 是日志收集服务,webappService 是 UI 服务。如果 oapService 和 webappService 部署在同一台机器上,Windows 环境下执行 startup.bat 文件即可同时启动 oapService 和 webappService,Linux 环境下执行 startup.sh 文件即可同时启动 oapService 和 webappService。如果 oapService 和 webappService 不在同一台机器上,则需分别执行对应的文件进行启动。
3.1.2.2. config 目录
config 目录中,主要是一些关于 oapService 的配置文件,用得最多的还是 application.yml 文件,一般都会对他的存储方式进行修改,默认是使用 h2 进行存储,可以修改为 Mysql 或 ES 等。
3.1.2.3. webapp 目录
webapp 目录中,存放的是 webappService(UI 界面-监控界面) 运行的 jar 包和配置文件。运行 skywalking-webapp.jar 包即可启动 UI 服务。在 application.yml 中,可以修改 UI 服务的端口及 oapService 的地址。
3.1.2.4. oap-libs 目录
oap-libs 目录中,是 oapService 运行的各种依赖包,其中 server-starter-9.6.0.jar 为 oapService 服务的启动包。
3.1.3. 服务部署
3.1.3.1. oapService 配置修改
进入到 config 目录,修改 application.yml 的配置信息,将 selector: ${SW_STORAGE:h2} 改为 selector: ${SW_STORAGE:mysql},如下所示。
同时配置 Mysql 连接信息(这里需要创建数据库 mall-skywalking,启动 oapService 会自动初始化数据库表),如下所示。
由于我们的数据存储方式是 Mysql ,oap-libs 目录中无 Mysql 连接驱动包,我们需要下载一个驱动包放进去。直接到我们本地工程的 Maven 仓库找到 mysql-connector-j-8.0.32.jar 驱动包,放进 oap-libs 目录即可。
oapService 服务启动后,会暴漏两个端口,分别是 11800 端口和 12800 端口,其中 11800 端口是负责日志收集的端口,即与 Java Agent 之间交互的端口;12800 端口是负责提供日志查询的端口,即与 webappService(UI 服务)之间交互的端口。
3.1.3.2. webappService 配置修改
由于 webappService(UI 服务) 默认端口是 8080,容易与其他应用的端口造成冲突,我们将端口修改为 8086。进入到 webapp 目录,修改 application.yml 配置文件。
其中,8086 就我们访问 UI 的端口,uri: http://127.0.0.1:12800 就是 oapService 的服务地址。在这里,我们是把 oapService 和 webappService 都部署在同一台机器,因此 127.0.0.1 无需修改。
3.1.4. 启动服务
我们是把 oapService 和 webappService 部署在 Windows 环境下的同一台机器的,进入到 bin 目录,直接双击 startup.bat 即可。首次启动涉及到初始化数据库,可能启动相对比较久, SkyWalking APM v9.2.0 版本,有 334 张表。当启动情况出现下面信息,且 logs 文件夹下的日志无报错信息,即说明 oapService 和 webappService 已经成功启动了。
此时可以看到,数据库表结构也被初始化了。
浏览器输入 http://localhost:808/ 地址,即可访问。
3.2. Java Agent
3.2.1. 文件下载
从官网下载 Java Agent v8.16.0 版本(链接地址),下载 apache-skywalking-java-agent-8.16.0.tgz 包,解压后文件目录如下。
3.2.2. 目录说明
Java Agent 下载解压后,我们主要是关注目录中的 skywalking-agent.jar 文件,因为启动微服务时,需要指定 skywalking-agent.jar 所在的路径,其他目录,都是 skywalking-agent.jar 文件运行时所依赖的一些包和插件。
3.2.3. 启动服务
Java Agent 是和我们的应用程序绑定一起启动的,可以参照官网的下列说明,对启动参数进行配置。
以订单服务为例,如果是在本地开发,用 idea 进行启动,可以在 VM options 中,按如下方式进行配置。
-javaagent:D:\apache-skywalking-java-agent-8.16.0\skywalking-agent.jar
-DSW_AGENT_NAME=mall-order-service
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
如果在 Linux 环境中,可以编写一个叫 mall-order-center-start.sh 的启动脚本, 如下所示。
#!/bin/sh
export SW_AGENT_NAME=mall-order-service
export SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
export JAVA_AGENT=-javaagent:/path/to/skywalking-package/agent/skywalking-agent.jar
java $JAVA_AGENT -jar mall-order-center-start-1.0-SNAPSHOT.jar
其中,SW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800 是 oapService 的地址。
由于我们是通过网关进行访问的,需要将 optional-plugins 文件夹中的 apm-spring-cloud-gateway-3.x-plugin-8.16.0.jar、apm-spring-webflux-5.x-plugin-8.16.0.jar 两个 jar 包拷贝到 plugins 文件中。
启动网关、账户服务、商品服务、订单服务后,打开 SkyWalking 控制台,找到“普通服务” -> “服务”,可以看到已绑定 Java Agent 的服务列表。
拓扑图如下。
4. 测试
我们在订单模块,调用”根据商品 id 查询商品信息“接口,输入不存在的商品id,如下所示。
idea 查看商品服务控制台报错日志如下。
日志说 89 行代码中 productEntity 对象不能为空。此时,我们去 SkyWalking 控制台也查看一下日志。找到“普通服务” -> “服务”,再查看拓扑图如下。
调用链路如下。
逐个点击橙色的端点,当点击到第三个橙色的端点(即:GET:/client/product/v1/queryById/1)时,可以看到 “java.lang.IllegalArgumentException: Source must not be null”异常,该异常正是 idea 控制台日志输出的异常。
5. 自定义链路追踪
到目前为止,我们在微服务工程中未添加一行代码,既已达到对微服务的基本监控。但是上面 “GET:/client/product/v1/queryById/1” 接口的异常信息,比起我们在 idea 看到的异常信息,少了一些重要的信息。例如,在上面的 idea 截图中,我们可以看到 ”org.example.product.app.executor.ProductExecutor.queryProductById(ProductExecutor.java:89)“ 的异常信息,这个信息非常重要,但是在上面“GET:/client/product/v1/queryById/1”接口的异常信息中未体现。为此,我们可以通过自定义链路追踪,来打印更多有用的追踪信息。
5.1. 异常信息打印
在商品模块的 app 层工程中添加如下 Maven 依赖。
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.15.0<</version>
</dependency>
在 org.example.product.app.executor.ProductExecutor#queryProductById 方法上添加 @Trace 注解,如下所示。
@Trace
public ProductRspDTO queryProductById(Long id) {
ProductEntity productEntity = this.productService.getById(id);
ProductRspDTO productRspDTO = new ProductRspDTO();
BeanUtils.copyProperties(productEntity,productRspDTO);
return productRspDTO;
}
重启商品服务,再次调用”根据商品 id 查询商品信息“接口,链路信息如下。
可以看到,链路中多了一个“
org.example.product.app.executor.ProductExecutor.queryProductById(java.lang.Long)”端点。点击进入查看该端点的日志信息,可以看到,已经打印出在 idea 中输出的那些重要信息了。
5.2. 方法参数打印
通过 @Trace 与 @Tag 注解的配合使用,我们可以打印业务方法的的入参、出参,例如下面的代码。
@Trace
@Tag(key = "inParam.id", value = "arg[0]")
@Tag(key = "outParam.productCode", value = "returnedObj.productCode")
@Tag(key = "outParam.count", value = "returnedObj.count")
@Tag(key = "outParam.productName", value = "returnedObj.productName")
@Tag(key = "outParam.price", value = "returnedObj.price")
public ProductRspDTO queryProductById(Long id) {
ProductEntity productEntity = this.productService.getById(id);
ProductRspDTO productRspDTO = new ProductRspDTO();
BeanUtils.copyProperties(productEntity,productRspDTO);
return productRspDTO;
}
Postman 对”根据商品 id 查询商品信息“接口,发送如下请求。
再查看”org.example.product.app.executor.ProductExecutor.queryProductById(java.lang.Long)“ 端点日志信息。可以看到,已经打印出了我们通过 @Tag 注解指定的出参、入参。
6. 性能监控
找到 ”仪表盘“->”仪表盘“->”General-Service“。
点击 ”General-Service“ 进去,给我们展示的是某个微服务整体的监控信息。
我们可以切到 ”Trace Profiling“选项卡创建对某个接口的监控任务。
例如对”根据商品 id 查询商品信息“接口做增加延时处理,如下所示。
接口监控任务,配置如下。
对接口发起请求后,监控信息如下。
此时,点击”分析“按钮,给我们展示了调用栈的分析结果。
其中”java.lang.Thread.sleep:-2“特别显眼,执行了 2979ms。
7. 日志集成
到目前为止,我们的微服务是没有输出任何链路追踪日志的,例如,调用”根据商品 id 查询商品信息“接口,商品服务日志信息如下。
如果要输出链路追踪日志,我们需要将 SkyWalking 与日志框架进行集成。SkyWalking官方提供了 log4j、log4j2、logback 的三种集成方式。在 Spring Boot 中,默认的日志集成框架是 logback,那么下面我们就来对 logback 进行集成。
给商品模块 app 层工程添加如下 Maven 依赖。
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.15.0<</version>
</dependency>
在商品服务 start 层工程 resource 目录下,新建 logback-spring.xml 文件,内容如下。
<configuration scan="true" scanPeriod=" 5 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>1024</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="STDOUT"/>
</appender>
<appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="ASYNC"/>
<appender-ref ref="grpc-log"/>
</root>
</configuration>
重启商品服务服务,重新请求接口,控制台就可以看到 TID 了。
将 TID 复制到 UI 控制台界面进行查询,效果如下。
此时 Log 选项卡的日志信息仍旧为空,需要对 Jave Agent 的 agent.config 配置文件,添加相关的配置信息才会将日志信息传输到 oapService。
给 agent.config 配置文件添加以下内容。
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:127.0.0.1}
plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800}
plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760}
plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}
重启商品服务服务,重新请求接口,UI 控制台界面就可以看到日志信息了。
点击点击”追踪ID“ 即可跳转到追踪页面。
8. 总结
本篇先分别介绍了 oapService(平台后端)、webappService(UI 服务)、 Java Agent(探测器)的安装和测试。接着介绍了在默认的基础上,如何增加打印业务方法的具体异常信息和方法参数。然后介绍了如何对接口进行性能监控和分析。最后介绍了如何集成日志框架和添加链路追踪id、追踪日志。通过异常信息的收集,性能的监控和分析,追踪日志的集成,我们可以更清晰的观测分布式系统。
基础篇项目代码:链接地址