在微服务架构盛行的今天,一个请求往往需要穿越多个服务才能完成。当系统出现问题时,开发者就像在迷宫中寻找出口,面对成百上千的服务实例和日志,定位问题变得异常艰难。Apache SkyWalking 作为一款优秀的分布式追踪系统,能帮助我们打开微服务的 "黑盒",让系统运行状态一目了然。本文将从理论到实践,全方位解析 SkyWalking 的核心原理与实战技巧,让你轻松掌握分布式系统的可观测性建设。
一、SkyWalking 前世今生:为什么需要分布式追踪?
随着微服务架构的普及,系统变得越来越复杂,传统的监控手段已经难以满足需求。想象一下,当用户投诉某个功能响应缓慢时,你需要排查 API 网关、多个微服务、数据库、缓存等多个组件,这个过程如果没有有效的工具支持,简直是一场噩梦。
1.1 分布式系统的可观测性挑战
分布式系统面临的可观测性挑战主要体现在三个方面:
- 调用链追踪困难:一个请求可能经过多个服务,传统日志分散在各个服务中,难以串联起来分析。
- 性能瓶颈定位难:系统响应慢,到底是哪个服务、哪个方法、哪个数据库查询出了问题?
- 服务依赖关系复杂:随着服务数量增加,服务间的依赖关系变得错综复杂,难以梳理。
这些挑战催生了分布式追踪系统的发展,而 SkyWalking 正是其中的佼佼者。
1.2 SkyWalking 简介
Apache SkyWalking 是一个开源的可观测性平台,专为微服务、云原生和容器化应用设计。它提供了分布式追踪、服务网格遥测分析、度量聚合和日志收集等功能,帮助开发者洞察分布式系统的运行状态。
SkyWalking 的核心优势:
- 多语言自动探针,支持 Java、.NET、Node.js 等多种语言
- 自动发现服务拓扑和依赖关系
- 强大的度量分析和告警能力
- 支持多种存储后端(Elasticsearch、MySQL、TiDB 等)
- 丰富的可视化界面
- 高性能,对业务代码侵入性小
1.3 分布式追踪的核心概念
在深入学习 SkyWalking 之前,我们需要了解几个分布式追踪的核心概念:

- 追踪(Trace):代表一个完整的请求链路,由多个跨度组成,用一个全局唯一的 Trace ID 标识。
- 跨度(Span):代表一个服务处理请求的单元,包含开始时间、结束时间、操作名称等信息,用 Span ID 标识。
- 标签(Tag):键值对,用于记录跨度的详细信息,如 HTTP 方法、URL、数据库语句等。
- 注解(Annotation):用于记录关键事件,如客户端发送请求(Client Send)、服务端接收请求(Server Receive)等。
- 采样率(Sampling Rate):由于高并发场景下追踪所有请求会带来性能开销,通常会设置采样率,只追踪一部分请求。
二、SkyWalking 架构深度解析
SkyWalking 采用模块化设计,各组件职责清晰,便于扩展和定制。理解其架构有助于我们更好地部署和使用 SkyWalking。
2.1 整体架构
SkyWalking 的架构可以分为四个主要部分:

- 探针(Agent):嵌入到应用程序中,负责收集追踪数据和度量信息,无需修改业务代码。
- 服务端(OAP Server):接收探针发送的数据,进行分析、聚合和存储,并提供查询接口。
- 存储(Storage):存储分析后的追踪数据、度量信息和告警数据,支持多种存储后端。
- UI:提供可视化界面,用于展示和查询追踪数据、服务拓扑、性能指标等。
2.2 数据流转过程
SkyWalking 的数据流转过程如下:

- 应用程序运行时产生服务调用
- Agent 拦截这些调用,生成 Span 和度量数据
- Agent 将数据批量发送到 OAP Server
- OAP Server 对数据进行分析和聚合
- 处理后的数据存储到指定的存储后端
- 用户通过 UI 查询和可视化数据
2.3 核心技术原理
SkyWalking 的实现基于字节码增强技术,这也是它能做到对业务代码零侵入的关键:
- 字节码增强:Agent 通过 Java Instrumentation API 在类加载时修改字节码,插入追踪代码。
- 跨进程传播:通过在服务间传递 Trace ID 和 Span ID,实现跨服务的调用链追踪。
- 服务发现:通过分析服务间的调用关系,自动发现服务拓扑。
- 指标计算:基于追踪数据计算各种性能指标,如响应时间、吞吐量、错误率等。
三、SkyWalking 环境搭建实战
接下来,我们将一步步搭建 SkyWalking 环境,包括 OAP Server、UI 和存储后端的部署。
3.1 环境准备
本次实战使用以下版本:
- SkyWalking 9.7.0(最新稳定版)
- Elasticsearch 8.11.3(存储后端)
- JDK 17(运行环境)
- Spring Boot 3.2.0(演示应用)
3.2 部署 Elasticsearch
SkyWalking 推荐使用 Elasticsearch 作为存储后端,我们先来部署 Elasticsearch:
-
下载 Elasticsearch 8.11.3:
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.11.3-linux-x86_64.tar.gz
tar -zxvf elasticsearch-8.11.3-linux-x86_64.tar.gz
cd elasticsearch-8.11.3 -
修改配置文件 config/elasticsearch.yml:
cluster.name: skywalking-es-cluster
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts: ["localhost"]
cluster.initial_master_nodes: ["node-1"]关闭安全特性(仅用于演示环境)
xpack.security.enabled: false
xpack.security.enrollment.enabled: false
xpack.security.http.ssl.enabled: false
xpack.security.transport.ssl.enabled: false -
启动 Elasticsearch:
创建专用用户(Elasticsearch不允许root用户运行)
useradd esuser
chown -R esuser:esuser elasticsearch-8.11.3
su esuser启动
./bin/elasticsearch -d
-
验证 Elasticsearch 是否启动成功:
如果返回类似以下内容,说明启动成功:
{
"name" : "node-1",
"cluster_name" : "skywalking-es-cluster",
"cluster_uuid" : "xxxxxxxxxxxxxxxxxxxx",
"version" : {
"number" : "8.11.3",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "f561230f37a86c8929ca1a400f2d00f7128049",
"build_date" : "2023-12-08T11:33:43.978089850Z",
"build_snapshot" : false,
"lucene_version" : "9.8.0",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}
3.3 部署 SkyWalking OAP Server
-
下载 SkyWalking 9.7.0:
wget https://archive.apache.org/dist/skywalking/9.7.0/apache-skywalking-apm-9.7.0.tar.gz
tar -zxvf apache-skywalking-apm-9.7.0.tar.gz
cd apache-skywalking-apm-bin -
修改配置文件 config/application.yml,配置 Elasticsearch 作为存储:
storage:
selector: elasticsearch
elasticsearch:
namespace: skywalking
clusterNodes: localhost:9200
protocol: http
# 其他配置保持默认 -
启动 OAP Server:
Linux
bin/oapService.sh start
Windows
bin/oapService.bat
3.4 部署 SkyWalking UI
SkyWalking UI 已经包含在下载的安装包中,无需单独下载:
-
修改 UI 配置文件 webapp/application.yml(可选):
server:
port: 8080 # UI端口,默认8080collector:
path: /graphql
ribbon:
ReadTimeout: 10000
# OAP Server地址
listOfServers: localhost:12800 -
启动 UI:
Linux
bin/webappService.sh start
Windows
bin/webappService.bat
-
访问 UI:打开浏览器,访问http://localhost:8080,看到 SkyWalking 的登录界面即表示部署成功(默认无需登录)。
四、Spring Boot 应用集成 SkyWalking
接下来,我们将创建一个 Spring Boot 应用,并集成 SkyWalking Agent,实现分布式追踪。
4.1 创建 Spring Boot 项目
首先创建一个 Spring Boot 项目,pom.xml 配置如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>skywalking-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>skywalking-demo</name>
<description>Demo project for SkyWalking integration</description>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<lombok.version>1.18.30</lombok.version>
<fastjson2.version>2.0.32</fastjson2.version>
<guava.version>32.1.3-jre</guava.version>
<springdoc.version>2.1.0</springdoc.version>
<mysql.version>8.0.33</mysql.version>
</properties>
<dependencies>
<!-- Spring Boot核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- FastJSON2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 创建数据库表
创建一个简单的用户表用于演示:
CREATE DATABASE IF NOT EXISTS skywalking_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE skywalking_demo;
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`age` int DEFAULT NULL COMMENT '年龄',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 插入测试数据
INSERT INTO `user` (`username`, `password`, `email`, `age`) VALUES
('test1', '123456', 'test1@example.com', 20),
('test2', '123456', 'test2@example.com', 25),
('test3', '123456', 'test3@example.com', 30);
4.3 配置文件
创建 application.yml 配置文件:
spring:
datasource:
url: jdbc:mysql://localhost:3306/skywalking_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus配置
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.example.skywalkingdemo.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 日志配置
logging:
level:
com.example.skywalkingdemo: debug
# 服务器配置
server:
port: 8081
servlet:
context-path: /user-service
4.4 实体类
创建 User 实体类:
package com.example.skywalkingdemo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户实体类
*
* @author ken
*/
@Data
@TableName("user")
public class User {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private String email;
/**
* 年龄
*/
private Integer age;
/**
* 创建时间
*/
@TableField(value = "create_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
4.5 Mapper 接口
创建 UserMapper 接口:
package com.example.skywalkingdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.skywalkingdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户Mapper接口
*
* @author ken
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
4.6 服务层
创建 UserService 接口和实现类:
package com.example.skywalkingdemo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.skywalkingdemo.entity.User;
import java.util.List;
/**
* 用户服务接口
*
* @author ken
*/
public interface UserService extends IService<User> {
/**
* 创建用户
*
* @param user 用户信息
* @return 创建的用户
*/
User createUser(User user);
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 用户信息
*/
User getUserById(Long id);
/**
* 获取所有用户
*
* @return 用户列表
*/
List<User> getAllUsers();
/**
* 更新用户
*
* @param user 用户信息
* @return 是否更新成功
*/
boolean updateUser(User user);
/**
* 删除用户
*
* @param id 用户ID
* @return 是否删除成功
*/
boolean deleteUser(Long id);
}
package com.example.skywalkingdemo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.skywalkingdemo.entity.User;
import com.example.skywalkingdemo.mapper.UserMapper;
import com.example.skywalkingdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.util.List;
/**
* 用户服务实现类
*
* @author ken
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 创建用户
*
* @param user 用户信息
* @return 创建的用户
*/
@Override
@Transactional(rollbackFor = Exception.class)
public User createUser(User user) {
log.info("创建用户: {}", user);
if (ObjectUtils.isEmpty(user)) {
throw new IllegalArgumentException("用户信息不能为空");
}
baseMapper.insert(user);
return user;
}
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 用户信息
*/
@Override
public User getUserById(Long id) {
log.info("根据ID获取用户: {}", id);
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
return baseMapper.selectById(id);
}
/**
* 获取所有用户
*
* @return 用户列表
*/
@Override
public List<User> getAllUsers() {
log.info("获取所有用户");
return baseMapper.selectList(new QueryWrapper<>());
}
/**
* 更新用户
*
* @param user 用户信息
* @return 是否更新成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateUser(User user) {
log.info("更新用户: {}", user);
if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
throw new IllegalArgumentException("用户ID不能为空");
}
return baseMapper.updateById(user) > 0;
}
/**
* 删除用户
*
* @param id 用户ID
* @return 是否删除成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteUser(Long id) {
log.info("删除用户: {}", id);
if (ObjectUtils.isEmpty(id)) {
throw new IllegalArgumentException("用户ID不能为空");
}
return baseMapper.deleteById(id) > 0;
}
}
4.7 控制层
创建 UserController:
package com.example.skywalkingdemo.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.skywalkingdemo.entity.User;
import com.example.skywalkingdemo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;
import java.util.List;
/**
* 用户控制器
*
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/users")
@Tag(name = "用户管理", description = "用户CRUD操作")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 创建用户
*
* @param user 用户信息
* @return 创建的用户
*/
@PostMapping
@Operation(summary = "创建用户", description = "添加新用户到系统")
public ResponseEntity<User> createUser(
@Parameter(description = "用户信息", required = true) @RequestBody User user) {
User createdUser = userService.createUser(user);
return ResponseEntity.ok(createdUser);
}
/**
* 根据ID获取用户
*
* @param id 用户ID
* @return 用户信息
*/
@GetMapping("/{id}")
@Operation(summary = "根据ID获取用户", description = "通过用户ID查询用户详情")
public ResponseEntity<User> getUserById(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
User user = userService.getUserById(id);
if (ObjectUtils.isEmpty(user)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
/**
* 获取所有用户
*
* @return 用户列表
*/
@GetMapping
@Operation(summary = "获取所有用户", description = "查询系统中所有用户")
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
/**
* 分页获取用户
*
* @param page 页码
* @param size 每页数量
* @return 分页用户列表
*/
@GetMapping("/page")
@Operation(summary = "分页获取用户", description = "分页查询系统中的用户")
public ResponseEntity<IPage<User>> getUsersByPage(
@Parameter(description = "页码", required = true) @RequestParam(defaultValue = "1") Integer page,
@Parameter(description = "每页数量", required = true) @RequestParam(defaultValue = "10") Integer size) {
Page<User> userPage = new Page<>(page, size);
IPage<User> users = userService.page(userPage);
return ResponseEntity.ok(users);
}
/**
* 更新用户
*
* @param id 用户ID
* @param user 用户信息
* @return 更新结果
*/
@PutMapping("/{id}")
@Operation(summary = "更新用户", description = "根据ID更新用户信息")
public ResponseEntity<Boolean> updateUser(
@Parameter(description = "用户ID", required = true) @PathVariable Long id,
@Parameter(description = "用户信息", required = true) @RequestBody User user) {
user.setId(id);
boolean updated = userService.updateUser(user);
return ResponseEntity.ok(updated);
}
/**
* 删除用户
*
* @param id 用户ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "根据ID删除用户")
public ResponseEntity<Boolean> deleteUser(
@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
boolean deleted = userService.deleteUser(id);
return ResponseEntity.ok(deleted);
}
}
4.8 配置类
创建 MyBatis-Plus 分页插件配置类:
package com.example.skywalkingdemo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis-Plus配置类
*
* @author ken
*/
@Configuration
public class MyBatisPlusConfig {
/**
* 配置分页插件
*
* @return MybatisPlusInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件,指定数据库类型为MySQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.9 启动类
创建项目启动类:
package com.example.skywalkingdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
/**
* SkyWalking示例项目启动类
*
* @author ken
*/
@SpringBootApplication
@MapperScan("com.example.skywalkingdemo.mapper")
@OpenAPIDefinition(
info = @Info(
title = "SkyWalking实战示例API",
version = "1.0",
description = "SkyWalking集成示例项目的API文档"
)
)
public class SkywalkingDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SkywalkingDemoApplication.class, args);
}
}
4.10 集成 SkyWalking Agent
要让应用程序被 SkyWalking 监控,需要在启动时指定 SkyWalking Agent:
-
从 SkyWalking 安装包中复制 agent 目录到合适的位置,例如项目根目录
-
修改 agent/config/agent.config 配置文件,设置应用名称和 OAP Server 地址:
应用名称,将在SkyWalking UI中显示
agent.service_name=${SW_AGENT_NAME:user-service}
OAP Server地址
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:localhost:11800}
采样率,1表示100%采样
agent.sample_n_per_3_secs=${SW_AGENT_SAMPLE:1}
-
使用以下命令启动 Spring Boot 应用(添加 - javaagent 参数):
java -javaagent:/path/to/agent/skywalking-agent.jar
-jar target/skywalking-demo-0.0.1-SNAPSHOT.jar
如果使用 Maven 插件启动,可以在 pom.xml 中配置:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>
-javaagent:/path/to/agent/skywalking-agent.jar
</jvmArguments>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
然后使用 Maven 命令启动:
mvn spring-boot:run
4.11 测试服务
启动应用后,可以通过 Swagger 文档测试接口:访问 http://localhost:8081/user-service/swagger-ui/index.html
调用几个接口后,我们就可以在 SkyWalking UI 中看到追踪数据了。
五、SkyWalking UI 功能详解
SkyWalking UI 提供了丰富的可视化功能,帮助我们分析和诊断分布式系统问题。
5.1 仪表盘(Dashboard)
仪表盘是 SkyWalking 的首页,展示了系统的关键指标:
- 服务吞吐量(Service Throughput):单位时间内的请求数
- 平均响应时间(Average Response Time):服务处理请求的平均时间
- 服务成功率(Service Success Rate):成功请求占总请求的比例
- 慢查询(Slow Traces):响应时间较长的请求
- 拓扑图(Topology):服务之间的依赖关系
5.2 服务(Services)
服务页面展示了所有被监控的服务列表,点击某个服务可以查看该服务的详细信息:
- 服务性能指标:响应时间、吞吐量、成功率随时间的变化曲线
- 实例列表:该服务的所有实例
- 端点列表:该服务的所有 API 端点
- 数据库访问:该服务访问的数据库及相关指标
- 缓存访问:该服务访问的缓存及相关指标
5.3 追踪(Traces)
追踪页面展示了所有被追踪的请求,我们可以通过以下方式筛选追踪数据:
- 按服务名称筛选
- 按响应时间筛选(可筛选慢查询)
- 按状态筛选(成功 / 失败)
- 按时间范围筛选
点击某个追踪可以查看详细的调用链:
- 每个跨度的响应时间
- 服务间的调用关系
- 每个跨度的详细信息(如 HTTP 方法、URL、数据库语句等)
5.4 拓扑图(Topology)
拓扑图展示了服务之间的依赖关系,以及服务与数据库、缓存等中间件的连接关系。通过拓扑图,我们可以直观地了解系统的整体架构。
拓扑图中的每个节点代表一个服务或中间件,节点之间的连线表示它们之间的调用关系,连线上的数字表示调用的 QPS。
5.5 告警(Alarms)
SkyWalking 支持基于指标设置告警规则,当指标超过阈值时会触发告警。告警页面展示了所有触发的告警信息,包括:
- 告警时间
- 告警级别
- 告警信息
- 关联的服务
六、SkyWalking 高级特性实战
6.1 自定义追踪上下文
有时候我们需要在追踪中添加自定义信息,SkyWalking 提供了 API 来实现这一功能:
package com.example.skywalkingdemo.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.skywalkingdemo.entity.User;
import com.example.skywalkingdemo.mapper.UserMapper;
import com.example.skywalkingdemo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.util.List;
/**
* 扩展用户服务实现类,添加SkyWalking自定义追踪
*
* @author ken
*/
@Slf4j
@Service
public class UserServiceImplWithTrace extends ServiceImpl<UserMapper, User> implements UserService {
/**
* 创建用户,添加自定义追踪信息
*
* @param user 用户信息
* @return 创建的用户
*/
@Override
@Transactional(rollbackFor = Exception.class)
public User createUser(User user) {
// 获取当前Trace ID
String traceId = TraceContext.traceId();
log.info("创建用户,Trace ID: {}", traceId);
if (ObjectUtils.isEmpty(user)) {
// 记录错误信息到追踪中
ActiveSpan.error(new IllegalArgumentException("用户信息不能为空"));
throw new IllegalArgumentException("用户信息不能为空");
}
// 添加自定义标签
ActiveSpan.tag("username", user.getUsername());
ActiveSpan.tag("user_info", JSON.toJSONString(user));
// 添加自定义日志
ActiveSpan.log("开始插入用户数据");
baseMapper.insert(user);
ActiveSpan.log("用户数据插入完成,ID: " + user.getId());
return user;
}
// 其他方法实现...
@Override
public User getUserById(Long id) {
log.info("根据ID获取用户: {}", id);
if (ObjectUtils.isEmpty(id)) {
ActiveSpan.error(new IllegalArgumentException("用户ID不能为空"));
throw new IllegalArgumentException("用户ID不能为空");
}
ActiveSpan.tag("user_id", id.toString());
return baseMapper.selectById(id);
}
@Override
public List<User> getAllUsers() {
log.info("获取所有用户");
return baseMapper.selectList(new QueryWrapper<>());
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateUser(User user) {
log.info("更新用户: {}", user);
if (ObjectUtils.isEmpty(user) || ObjectUtils.isEmpty(user.getId())) {
ActiveSpan.error(new IllegalArgumentException("用户ID不能为空"));
throw new IllegalArgumentException("用户ID不能为空");
}
ActiveSpan.tag("user_id", user.getId().toString());
return baseMapper.updateById(user) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteUser(Long id) {
log.info("删除用户: {}", id);
if (ObjectUtils.isEmpty(id)) {
ActiveSpan.error(new IllegalArgumentException("用户ID不能为空"));
throw new IllegalArgumentException("用户ID不能为空");
}
ActiveSpan.tag("user_id", id.toString());
return baseMapper.deleteById(id) > 0;
}
}
要使用 SkyWalking 的自定义追踪 API,需要添加以下依赖:
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>9.7.0</version>
</dependency>
6.2 分布式追踪实战:多服务调用
为了演示分布式追踪,我们再创建一个订单服务,调用前面的用户服务:
-
创建订单服务项目,pom.xml 与用户服务类似,额外添加 Spring Cloud OpenFeign 依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>4.1.0</version> </dependency> -
创建订单实体类:
package com.example.orderservice.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;/**
-
订单实体类
-
@author ken
/
@Data
@TableName("order")
public class Order {
/*- 主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
- 用户ID
*/
private Long userId;
/**
- 订单金额
*/
private BigDecimal amount;
/**
- 订单状态:0-待支付,1-已支付,2-已取消
*/
private Integer status;
/**
- 创建时间
*/
@TableField(value = "create_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
private LocalDateTime createTime;
/**
- 更新时间
*/
@TableField(value = "update_time", fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
- 主键ID
-
-
创建 Feign 客户端调用用户服务:
package com.example.orderservice.feign;
import com.example.orderservice.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;/**
-
用户服务Feign客户端
-
@author ken
*/
@FeignClient(name = "user-service", url = "http://localhost:8081/user-service")
public interface UserServiceFeignClient {/**
- 根据ID获取用户
- @param id 用户ID
- @return 用户信息
*/
@GetMapping("/api/users/{id}")
User getUserById(@PathVariable("id") Long id);
}
-
-
创建订单服务的控制器和服务类,在服务中调用用户服务:
package com.example.orderservice.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.orderservice.entity.Order;
import com.example.orderservice.entity.User;
import com.example.orderservice.feign.UserServiceFeignClient;
import com.example.orderservice.mapper.OrderMapper;
import com.example.orderservice.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;/**
-
订单服务实现类
-
@author ken
*/
@Slf4j
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final UserServiceFeignClient userServiceFeignClient;
public OrderServiceImpl(UserServiceFeignClient userServiceFeignClient) {
this.userServiceFeignClient = userServiceFeignClient;
}/**
-
创建订单,同时查询用户信息
-
@param order 订单信息
-
@return 创建的订单
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Order createOrder(Order order) {
log.info("创建订单: {}", order);
if (ObjectUtils.isEmpty(order) || ObjectUtils.isEmpty(order.getUserId())) {
ActiveSpan.error(new IllegalArgumentException("用户ID不能为空"));
throw new IllegalArgumentException("用户ID不能为空");
}// 调用用户服务查询用户信息
log.info("查询用户信息,用户ID: {}", order.getUserId());
User user = userServiceFeignClient.getUserById(order.getUserId());
if (ObjectUtils.isEmpty(user)) {
ActiveSpan.error(new IllegalArgumentException("用户不存在,ID: " + order.getUserId()));
throw new IllegalArgumentException("用户不存在,ID: " + order.getUserId());
}
log.info("查询到用户信息: {}", user);// 创建订单
baseMapper.insert(order);
return order;
}
// 其他方法实现...
@Override
public Order getOrderById(Long id) {
log.info("根据ID获取订单: {}", id);
if (ObjectUtils.isEmpty(id)) {
ActiveSpan.error(new IllegalArgumentException("订单ID不能为空"));
throw new IllegalArgumentException("订单ID不能为空");
}
return baseMapper.selectById(id);
}
} -
-
-
配置订单服务的 SkyWalking Agent,设置服务名为 order-service:
agent.service_name=${SW_AGENT_NAME:order-service}
-
分别启动用户服务和订单服务,调用订单服务的创建订单接口,然后在 SkyWalking UI 中查看完整的调用链。
6.3 告警配置
SkyWalking 支持基于各种指标设置告警规则,配置文件位于 OAP Server 的 config/alarm-settings.yml:
rules:
# 服务响应时间告警
service_resp_time_rule:
metrics-name: service_resp_time
op: ">"
threshold: 1000
period: 10
count: 3
silence-period: 5
message: "服务 {name} 响应时间超过1000ms,当前值: {value}ms"
# 服务错误率告警
service_error_rate_rule:
metrics-name: service_error_rate
op: ">"
threshold: 0.05
period: 10
count: 2
silence-period: 5
message: "服务 {name} 错误率超过5%,当前值: {value}"
# 端点响应时间告警
endpoint_resp_time_rule:
metrics-name: endpoint_resp_time
op: ">"
threshold: 500
period: 10
count: 3
silence-period: 5
message: "端点 {name} 响应时间超过500ms,当前值: {value}ms"
# 告警接收器配置
receivers:
default:
# 控制台输出
- console:
# 可以添加邮件、Slack等其他接收器
#- email:
# host: smtp.example.com
# port: 587
# username: alert@example.com
# password: password
# from: alert@example.com
# to: admin@example.com
# subject: "SkyWalking 告警"
配置说明:
- metrics-name:要监控的指标名称
- op:比较运算符(>、<、>=、<=、==)
- threshold:阈值
- period:检测周期(分钟)
- count:连续多少次超过阈值才触发告警
- silence-period:告警沉默期,在此期间不再发送相同的告警
- message:告警消息
修改配置后需要重启 OAP Server 使配置生效。
6.4 日志集成
SkyWalking 可以与日志框架集成,将 Trace ID 添加到日志中,方便将日志与追踪关联起来。
-
添加 logback 依赖:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.8</version> </dependency> -
创建 logback-spring.xml 配置文件:
<?xml version="1.0" encoding="UTF-8"?> <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>
-
添加 SkyWalking 日志工具依赖:
<dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-logback-1.x</artifactId> <version>9.7.0</version> </dependency>
这样配置后,日志中会包含 [TID:xxx] 字段,记录当前的 Trace ID,方便将日志与 SkyWalking 中的追踪关联起来。
七、SkyWalking 性能优化与最佳实践
7.1 性能优化建议
SkyWalking 本身的性能开销很小,但在高并发场景下,仍需要进行适当的优化:
-
调整采样率:在高并发场景下,可以降低采样率,减少数据量
# 每3秒最多采样10个追踪 agent.sample_n_per_3_secs=10
-
选择合适的存储:生产环境推荐使用 Elasticsearch,并根据数据量进行集群配置
-
调整 JVM 参数:为 OAP Server 分配足够的内存
# 在bin/oapService.sh中调整 JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC"
-
定期清理数据:配置 Elasticsearch 的索引生命周期管理,定期清理旧数据
-
使用异步 reporter:Agent 默认使用异步方式发送数据,确保此配置未被修改
agent.reporter.type=grpc
7.2 最佳实践
-
为所有服务配置有意义的名称:方便在 UI 中识别和筛选服务
agent.service_name=order-service
-
合理设置告警阈值:根据业务场景设置合适的告警阈值,避免告警风暴
-
集成日志与追踪:将 Trace ID 添加到日志中,方便问题排查
-
定期分析慢查询:利用 SkyWalking 的慢查询追踪功能,定期分析并优化慢查询
-
监控数据库性能:关注数据库操作的响应时间,及时发现数据库性能问题
-
跟踪服务依赖变化:通过拓扑图关注服务依赖关系的变化,及时发现不合理的依赖
-
结合业务指标:除了系统指标,还可以通过自定义指标监控业务指标
八、常见问题与解决方案
8.1 Agent 无法连接到 OAP Server
可能原因:
- OAP Server 未启动或端口错误
- 网络问题,Agent 无法访问 OAP Server
- 防火墙阻止了连接
解决方案:
- 检查 OAP Server 是否正常运行:
ps -ef | grep oap
- 检查 OAP Server 端口配置(默认 11800)
- 测试网络连通性:
telnet oap-server-ip 11800
- 检查防火墙规则,确保 11800 端口开放
8.2 UI 中看不到服务数据
可能原因:
- Agent 未正确配置或未随应用启动
- Agent 与 OAP Server 连接失败
- 应用未产生任何请求
- 采样率设置过低,未采集到数据
解决方案:
- 检查应用启动命令,确保 - javaagent 参数正确
- 查看应用日志,寻找与 SkyWalking 相关的错误信息
- 发送请求到应用,确保有流量产生
- 提高采样率:
agent.sample_n_per_3_secs=100
8.3 追踪数据不完整
可能原因:
- 部分服务未集成 SkyWalking Agent
- 服务间调用未正确传递 Trace 上下文
- 异步调用未正确处理 Trace 上下文
解决方案:
-
确保所有服务都集成了 SkyWalking Agent
-
检查服务间调用方式,确保支持 Trace 上下文传递(SkyWalking 对常见框架如 Spring Cloud、Dubbo 等都有支持)
-
对于异步调用,使用 SkyWalking 提供的工具类传递上下文:
import org.apache.skywalking.apm.toolkit.trace.TraceContext; import org.apache.skywalking.apm.toolkit.trace.TraceCrossThread; // 异步任务 @TraceCrossThread public class AsyncTask implements Runnable { @Override public void run() { // 异步任务逻辑 } }
8.4 OAP Server 内存占用过高
可能原因:
- 数据量过大
- JVM 内存配置不足
- 存储后端性能不佳,导致数据堆积
解决方案:
- 增加 OAP Server 的 JVM 内存配置
- 降低采样率,减少数据量
- 优化存储后端性能,如为 Elasticsearch 增加节点
- 配置数据保留策略,及时清理旧数据
九、总结与展望
Apache SkyWalking 作为一款优秀的分布式追踪系统,为微服务架构提供了强大的可观测性支持。通过本文的学习,我们了解了 SkyWalking 的核心概念、架构原理,掌握了环境搭建、应用集成和高级特性的使用方法。
SkyWalking 不仅能帮助我们追踪分布式系统中的请求流向,还能提供丰富的性能指标和告警功能,让我们能够及时发现和解决系统中的性能瓶颈和潜在问题。在实际项目中,合理使用 SkyWalking 可以显著提高系统的可靠性和可维护性。
希望本文能帮助你快速掌握 SkyWalking 的使用,让你的微服务系统从 "黑盒" 变为 "透明",让问题排查不再困难。