跳到主要内容SkyWalking 与 Spring Cloud Alibaba 全链路追踪实战 | 极客日志Javajava
SkyWalking 与 Spring Cloud Alibaba 全链路追踪实战
基于 Apache SkyWalking 和 Spring Cloud Alibaba 构建微服务全链路追踪方案。通过 Docker 部署 OAP Server 与 UI,集成 Java Agent 实现零侵入式监控。涵盖服务拓扑分析、手动埋点、日志关联及性能剖析等实战技巧,解决分布式系统故障定位难题。
CoderByte1 浏览 
在微服务架构日益普及的今天,一个请求往往需要穿越多个服务才能完成。这种分布式调用虽然带来了系统解耦和扩展性优势,但也给问题排查、性能分析和系统监控带来了巨大挑战。当用户反馈'系统变慢'或'请求失败'时,我们如何快速定位问题发生在哪个服务?是数据库慢了?还是某个中间件响应延迟?亦或是网络抖动?这时候,全链路追踪(Distributed Tracing) 就显得尤为重要。
Apache SkyWalking 是一款开源的 APM(Application Performance Monitoring)系统,专为微服务、云原生和容器化环境设计。它提供了强大的分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。而 Spring Cloud Alibaba 则是一套基于 Spring Cloud 的微服务开发工具集,整合了 Nacos、Sentinel、Seata 等中间件。
将 SkyWalking 与 Spring Cloud Alibaba 结合,可以构建一个功能强大、可观测性极佳的微服务系统。本文将带你从零开始,搭建一个基于 Spring Cloud Alibaba 的微服务项目,并集成 SkyWalking,实现端到端的全链路追踪。
环境准备与核心概念
在动手之前,我们需要先了解一些核心概念,并准备好必要的环境。
核心概念解析
- Trace(追踪): 一次完整的请求调用链。从用户发起请求到最终返回结果,整个过程中所有经过的服务和操作都属于同一个 Trace。
- Span(跨度): Trace 中的一个基本工作单元。例如,一个服务内部的方法调用、一次数据库查询、一次 HTTP 请求都可以是一个 Span。
- Segment(片段): SkyWalking 特有的概念。在一个 JVM 进程内,一次 Trace 可能会生成多个 Segment。每个 Segment 代表了该进程内的一段连续的执行路径。
- OAP Server: SkyWalking 的后端服务,负责接收、分析、聚合来自各个 Agent 的数据。
- Agent: 一个 Java Agent,以
-javaagent 参数的方式挂载到你的 Java 应用上。它会在不修改你代码的情况下,通过字节码增强技术自动收集应用的追踪数据。
环境准备
为了顺利进行本次实战,你需要准备以下环境:JDK 8+、Maven、Docker & Docker Compose、IDEA。
首先,我们需要启动 SkyWalking 和 Nacos。这里我们使用 docker-compose 来一键部署。
创建一个 docker-compose.yml 文件:
version: '3.8'
services:
nacos:
image: nacos/nacos-server:v2.2.3
container_name: nacos-standalone
environment:
- MODE=standalone
ports:
- "8848:8848"
- "9848:9848"
restart:
always
oap:
image:
apache/skywalking-oap-server:9.7.0
container_name:
skywalking-oap
ports:
-
"11800:11800"
-
"12800:12800"
environment:
-
SW_STORAGE=h2
depends_on:
-
nacos
restart:
always
ui:
image:
apache/skywalking-ui:9.7.0
container_name:
skywalking-ui
ports:
-
"8080:8080"
environment:
-
SW_OAP_ADDRESS=oap:12800
depends_on:
-
oap
restart:
always
在终端中执行 docker-compose up -d,等待几分钟,所有服务就会启动完毕。
- Nacos: 访问
http://localhost:8848/nacos,默认账号密码都是 nacos。
- SkyWalking UI: 访问
http://localhost:8080,即可看到 SkyWalking 的仪表盘。
现在,我们的基础设施已经就绪!接下来,让我们开始构建微服务应用。
构建 Spring Cloud Alibaba 微服务项目
我们将构建一个简单的电商场景:用户下单。这个场景涉及两个核心服务:
order-service: 订单服务,负责创建订单。
inventory-service: 库存服务,负责扣减库存。
order-service 会通过 Feign 调用 inventory-service。
创建父工程
首先,创建一个 Maven 父工程 skywalking-sca-demo,用于统一管理依赖版本。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>order-service</module>
<module>inventory-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2021.0.8</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
构建 inventory-service(库存服务)
这是一个简单的服务,提供一个 /deduct 接口用于扣减库存。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>inventory-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8082
spring:
application:
name: inventory-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
package com.example.inventory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class InventoryServiceApplication {
public static void main(String[] args) {
SpringApplication.run(InventoryServiceApplication.class, args);
}
}
package com.example.inventory.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/inventory")
public class InventoryController {
private static final Logger log = LoggerFactory.getLogger(InventoryController.class);
@PostMapping("/deduct")
public String deduct(@RequestParam String productId, @RequestParam Integer count) {
log.info("收到扣减库存请求:productId={}, count={}", productId, count);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("库存扣减成功");
return "success";
}
}
构建 order-service(订单服务)
这个服务会暴露一个 /create 接口,并通过 Feign 调用 inventory-service。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>skywalking-sca-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 8081
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
management:
endpoints:
web:
exposure:
include: '*'
package com.example.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
package com.example.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "inventory-service")
public interface InventoryFeignClient {
@PostMapping("/inventory/deduct")
String deduct(@RequestParam("productId") String productId, @RequestParam("count") Integer count);
}
package com.example.order.controller;
import com.example.order.feign.InventoryFeignClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/order")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Autowired
private InventoryFeignClient inventoryFeignClient;
@PostMapping("/create")
public String createOrder(@RequestParam String userId, @RequestParam String productId, @RequestParam Integer count) {
log.info("收到创建订单请求:userId={}, productId={}, count={}", userId, productId, count);
String result = inventoryFeignClient.deduct(productId, count);
log.info("订单创建成功");
return "order created, inventory result: " + result;
}
}
验证基础功能
现在,我们可以分别启动两个服务。访问 Nacos 控制台,你应该能看到 order-service 和 inventory-service 都已成功注册。
然后,使用 curl 或 Postman 调用订单服务:
curl -X POST "http://localhost:8081/order/create?userId=1&productId=P001&count=1"
如果返回 order created, inventory result: success,说明我们的微服务调用链路是通的!然而,此时如果我们想了解这次调用的详细耗时、经过了哪些服务,或者排查性能瓶颈,我们无从下手。这就是 SkyWalking 大显身手的时候了。
集成 SkyWalking Agent
SkyWalking 的核心在于其 Java Agent。它通过字节码增强技术,在应用运行时动态地注入追踪逻辑,无需修改一行业务代码。
下载 SkyWalking Agent
访问 Apache SkyWalking 官方下载页面,下载与你的 OAP Server 版本(9.7.0)匹配的 Agent 包。通常选择 apache-skywalking-java-agent-*.tar.gz。
下载后,将其解压到一个目录,例如 /opt/skywalking-agent。
配置 Agent
我们需要对 config/agent.config 文件进行一些关键配置。
我们将为两个服务创建各自的配置文件,以区分它们在 SkyWalking UI 中的显示。
为 inventory-service 创建配置:
在 skywalking-agent/config/ 目录下创建 inventory-service.config:
agent.service_name=inventory-service
collector.backend_service=127.0.0.1:11800
为 order-service 创建配置:
在 skywalking-agent/config/ 目录下创建 order-service.config:
agent.service_name=order-service
collector.backend_service=127.0.0.1:11800
启动应用并挂载 Agent
现在,我们需要通过 -javaagent 参数来启动我们的 Java 应用。
启动 inventory-service
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.config_path=/opt/skywalking-agent/config/inventory-service.config \
-jar inventory-service/target/inventory-service-1.0.0.jar
启动 order-service
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.config_path=/opt/skywalking-agent/config/order-service.config \
-jar order-service/target/order-service-1.0.0.jar
启动成功后,你会在应用的日志中看到类似 [SkyWalking Agent] ... 的信息,表明 Agent 已成功加载。
验证追踪效果
再次调用我们的订单接口,然后打开 SkyWalking UI (http://localhost:8080)。
- 查看服务拓扑图: 在左侧菜单栏点击 Topology。你会看到一个清晰的拓扑图,展示了
order-service 和 inventory-service 之间的调用关系。
- 查看追踪列表: 点击 Trace 菜单。在这里,你可以看到所有被追踪的请求。找到你刚刚发起的请求,点击它。
- 分析追踪详情: 在追踪详情页,你会看到一个非常直观的瀑布流图(Gantt Chart)。它清晰地展示了整个调用链路。通过这个图,你可以一目了然地看出哪个环节最耗时。
深入 SkyWalking:自定义追踪与告警
虽然 SkyWalking Agent 已经为我们自动追踪了大部分场景,但在某些复杂的业务逻辑中,我们可能希望手动创建 Span 来追踪特定的代码块,或者设置告警规则。
手动埋点(Manual Context Propagation)
假设在 order-service 中,除了调用库存服务,我们还需要记录一个重要的业务日志到数据库,并且我们希望这个数据库操作也能体现在追踪链路中。
- 添加数据库依赖: 在
order-service/pom.xml 中添加 H2 数据库和 MyBatis 依赖。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: password
- 在 Service 中手动埋点: SkyWalking 提供了
TraceContext API 来手动创建 Span。
package com.example.order.service;
import com.example.order.mapper.OrderMapper;
import com.example.order.model.Order;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
@Autowired
private InventoryFeignClient inventoryFeignClient;
@Autowired
private OrderMapper orderMapper;
@Trace
public String createOrder(String userId, String productId, Integer count) {
log.info("开始创建订单");
String before = TraceContext.traceId();
try (final var ignored = TraceContext.createLocalSpan("OrderService.saveToDB")) {
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
orderMapper.insert(order);
log.info("订单已保存到数据库");
}
String result = inventoryFeignClient.deduct(productId, count);
log.info("订单创建流程结束");
return "success";
}
}
同时,别忘了在 Controller 中调用这个 Service。现在,当你再次发起请求并在 SkyWalking UI 中查看追踪详情时,你会发现多了一个名为 OrderService.saveToDB 的 Span。
配置告警
SkyWalking 不仅能追踪,还能根据预设的规则进行告警。
SkyWalking 的告警规则配置在 OAP Server 的 config/alarm-settings.yml 文件中。由于我们是用 Docker 启动的,需要进入容器进行修改。
- 添加一个简单的告警规则: 例如,我们希望当
order-service 的 /order/create 接口的平均响应时间超过 500ms 时,触发告警。
rules:
service_resp_time_rule:
metrics-name: service_resp_time
threshold: 500
period: 10
count: 1
silence-period: 10
include-names:
- order-service
message: Response time of service {name} is more than {value} ms in {period} minutes.
重启 OAP 容器:
修改完配置后,需要重启容器使配置生效。
docker restart skywalking-oap
现在,如果你故意在 order-service 中增加一个长时间的 Thread.sleep(600),然后持续调用接口,SkyWalking 就会触发告警。
高级特性与最佳实践
掌握了基础用法后,让我们探讨一些更高级的特性和在生产环境中应该遵循的最佳实践。
日志集成(Log Integration)
追踪(Trace)和日志(Log)是可观测性的两大支柱。将它们关联起来,可以极大地提升问题排查效率。
- 配置
logback-spring.xml: 在 resources 目录下创建 logback-spring.xml 文件。
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.16.0</version>
</dependency>
<configuration>
<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.TraceIdPatternLogbackLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n</pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
关键点在于 %tid,它会被 SkyWalking Agent 自动替换为当前的 TraceId。
性能剖析(Profile)
当某个接口突然变慢,但常规的追踪无法定位到具体是哪一行代码导致的,这时就可以使用 SkyWalking 的 Performance Profiling 功能。
- 在 SkyWalking UI 中,进入 Trace 页面,找到那个慢的请求。
- 在该 Trace 的详情页,点击右上角的 Profile 按钮。
- 设置采样间隔和最大采样数。
- 任务完成后,你可以在 Profile 菜单中看到详细的火焰图(Flame Graph),它能精确地告诉你 CPU 时间都花在了哪些方法上。
生产环境最佳实践
- 存储选型: 本文为了方便,使用了 H2 内存数据库。但在生产环境中,必须使用 Elasticsearch 作为 SkyWalking 的后端存储。
- Agent 升级: 尽量保持 Agent 和 OAP Server 的版本一致。
- 采样率: 对于高流量的应用,可以适当调整采样率,避免上报过多数据给 OAP Server 带来压力。
- 安全: OAP Server 的 gRPC 和 HTTP 端口应做好网络安全隔离。
总结与展望
通过本文的实战,我们成功地将 Apache SkyWalking 集成到了一个基于 Spring Cloud Alibaba 的微服务项目中。我们不仅实现了自动化的全链路追踪,还学习了如何手动埋点、配置告警、集成日志以及使用性能剖析等高级功能。
SkyWalking 以其无侵入性、强大的可视化能力和丰富的生态系统,成为了微服务可观测性领域的佼佼者。它帮助我们从'盲人摸象'式的故障排查,转变为'上帝视角'下的精准定位,极大地提升了研发和运维效率。
对于每一位致力于构建稳定、高效、可维护的现代应用的开发者来说,掌握 SkyWalking 这样的 APM 工具,已经成为一项必备技能。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online