1、概述
1.1 什么是 SkyWalking?
SkyWalking:是一个开源的 APM(应用性能监控)和可观测性分析平台,由 Apache 软件基金会孵化并成为顶级项目。
Skywalking 专为微服务、云原生架构和基于容器的架构设计,提供了分布式追踪、服务网格遥测分析、度量聚合和可视化一体化的解决方案。
SkyWalking 的主要功能包括:
- 分布式追踪:追踪分布式系统中的请求流,记录请求在各个组件之间的传递过程,识别性能瓶颈。
- 性能监控:监控关键性能指标,如响应时间、吞吐量等,帮助了解系统的整体性能表现。
- 问题排查:提供详细的跟踪信息,帮助快速定位和解决系统中的问题。
- 可视化界面:提供丰富的图表分析功能,如拓扑图、调用链路分析、性能趋势等。
- 告警和报警:设置告警规则,当系统出现异常或性能指标超过预设阈值时,及时通知相关人员。
1.2 什么是链路追踪?
链路追踪(Distributed Tracing):是一种用于监控和诊断分布式系统中请求处理的技术。
在分布式系统中,一个用户的请求可能会经过多个服务节点,每个服务节点可能是不同团队开发、部署在不同的服务器上,甚至可能使用不同的编程语言编写。链路追踪的目的是追踪请求在这些服务节点中的传播路径,以便于开发者能够理解请求的执行流程、性能瓶颈和故障点。
链路追踪系统通常包括以下几个关键概念:
- Trace:表示一次完整的请求处理过程,从客户端发起请求到服务器返回响应结束。每个 Trace 有一个唯一的 Trace ID 来标识。
- Span:是 Trace 的基本单元,代表请求在单个服务节点上的处理过程。每个 Span 有自己的 Span ID,并且包含在特定的 Trace 中。
- Span Context:用于在分布式系统的各个服务节点之间传播请求上下文的信息。通常包括 Trace ID 和 Span ID。
- Annotation/Log Tag:在 Span 中记录的时间点,用于标记特定的事件,如请求的开始和结束。日志标签可以用于记录额外的信息,如错误日志、关键业务逻辑等。
- Service:表示分布式系统中的一个服务节点。
- Service Instance:表示服务的具体实例,如一个运行中的 Pod 或一个 JVM 进程。
- Endpoint:表示服务中的具体操作,如 HTTP API 的路径或数据库的查询语句。
1.3 SkyWalking 的架构
1)探针(Agent) :是 SkyWalking 的客户端库,它被集成到应用程序中,负责收集应用运行时的性能数据,如方法调用、服务调用、异常等。
- 探针以非侵入的方式工作,对应用程序的性能影响非常小。
- 探针支持多种语言,包括 Java、.NET Core、Node.js、Go 等,这使得SkyWalking能够监控多种技术栈的应用程序。
- 探针收集的数据包括 Trace Segment(追踪段)、Log、Metric 等,这些数据被发送到 OAP 服务器进行进一步处理。
2)平台后端(OAP) :是 SkyWalking 的服务器端,负责接收探针发送的数据,并进行数据的聚合、分析和存储。
- OAP 支持水平扩展,可以通过增加节点来处理更多的数据。
- OAP 提供了丰富的 API,允许第三方系统和工具与 SkyWalking 集成。
- OAP 还支持多种语言的协议适配器,如 gRPC、HTTP 等。
3)展示页面(UI) :SkyWalking 的 UI 提供了一个直观的界面,用于展示监控数据和分析结果。
- UI 支持多种图表和视图,如拓扑图、服务地图、调用链、性能指标等,帮助用户快速了解系统的状态。
- UI 支持自定义和扩展,用户可以根据自己的需求定制视图和仪表板。
- UI 还提供了告警和通知功能,当系统出现异常时,可以及时通知运维人员。
4)存储(Storage) :通过开放/可插入的界面存储 SkyWalking 数据,SkyWalking 支持多种存储解决方案,以适应不同的部署需求和性能要求。如:
- H2:是 SkyWalking 自带的轻量级内存数据库,适用于演示和开发环境。
- MySQL:是一个稳定且广泛使用的数据库系统,但查询速度可能不如一些专门的 NoSQL 数据库,适合于长时间应用性能监控。
- ElasticSearch:是一个分布式、RESTful 搜索和分析引擎,通常用于处理大规模数据集。SkyWalking 官方推荐使用 ElasticSearch,因为它提供了快速的查询性能,尤其是在处理大量追踪记录时。
- BanyanDB:是 SkyWalking 的原生存储解决方案,旨在提供高性能和资源效率。与 ElasticSearch 相比,在 CPU、内存和磁盘使用上都有明显的优势,适合中等规模的部署。
- ClickHouse:是一个用于在线分析处理(OLAP)的列式数据库系统,它在处理大量数据时表现出色,尤其是在查询性能和响应时间上。
1.4 SkyWalking 的配置
1)application.yml:是 SkyWalking OAP 服务器的主配置文件,定义了 OAP 服务器的运行时参数。以下是一些常见的配置项:
# 集群配置
cluster:
# 选择器:选择使用的集群类型,默认 standalone
selector: ${SW_CLUSTER:standalone}
# 独立模式:不需要额外的集群协调服务。
standalone:
...
# 与 ZooKeeper 集成时的参数
zookeeper:
# ZooKeeper 的命名空间,默认为空。
namespace: ${SW_NAMESPACE:""}
# ZooKeeper 服务的地址和端口,默认为 localhost:2181。
hostPort: ${SW_CLUSTER_ZK_HOST_PORT:localhost:2181}
...
# 与 Kubernetes 集成时的参数
kubernetes:
# Kubernetes 的命名空间,默认为 default。
namespace: ${SW_CLUSTER_K8S_NAMESPACE:default}
...
# 与 Consul 集成时的参数
consul:
# Consul 中的服务名称,默认为 SkyWalking_OAP_Cluster。
serviceName: ${SW_SERVICE_NAME:"SkyWalking_OAP_Cluster"}
# Consul 服务的地址和端口,默认为 localhost:8500。
hostPort: ${SW_CLUSTER_CONSUL_HOST_PORT:localhost:8500}
...
# 与 etcd 集成时的参数
etcd:
# etcd 服务的地址和端口,默认为 localhost:2379。
endpoints: ${SW_CLUSTER_ETCD_ENDPOINTS:localhost:2379}
# etcd 的命名空间,默认为 /skywalking。
namespace: ${SW_CLUSTER_ETCD_NAMESPACE:/skywalking}
...
# 与 Nacos 集成时的参数
nacos:
# Nacos 中的服务名称,默认为 SkyWalking_OAP_Cluster。
serviceName: ${SW_SERVICE_NAME:"SkyWalking_OAP_Cluster"}
# Nacos 服务的地址和端口,默认为 localhost:8848。
hostPort: ${SW_CLUSTER_NACOS_HOST_PORT:localhost:8848}
...
# 核心配置
core:
# 选择器:选择核心配置,默认为 default。
selector: ${SW_CORE:default}
default:
# OAP 服务器的角色,默认为 Mixed,可以是 Mixed、Receiver 或 Aggregator。
role: ${SW_CORE_ROLE:Mixed}
# REST API 服务的主机地址,默认为 0.0.0.0(所有网络接口)。
restHost: ${SW_CORE_REST_HOST:0.0.0.0}
# REST API 服务的端口,默认为 12800。
restPort: ${SW_CORE_REST_PORT:12800}
# gRPC 服务的主机地址,默认为 0.0.0.0。
gRPCHost: ${SW_CORE_GRPC_HOST:0.0.0.0}
# gRPC 服务的端口,默认为 11800。
gRPCPort: ${SW_CORE_GRPC_PORT:11800}
...
# 存储配置
storage:
# 选择器:选择存储类型,默认为 h2。
selector: ${SW_STORAGE:h2}
# 使用 Elasticsearch 数据库时的参数。
elasticsearch:
...
# 使用 H2 数据库时的参数
h2:
...
# 使用 MySQL 数据库时的参数。
mysql:
...
# 使用 PostgreSQL 数据库时的参数。
postgresql:
...
# 使用 BanyanDB 时的参数。
banyandb:
...
2)alarm-settings.yml:是 SkyWalking 的告警规则配置文件,它定义了告警规则和触发条件。以下是一些常见的配置项:
rules:
# 自定义告警规则名称
xxx:
# 数学表达式,必须是比较操作,结果为 1 或 0,当结果为 1 时,将触发告警
expression:
# 评估指标的时间长度,单位为分钟
period:
# 在告警触发后,处于静默状态的时间(即使触发告警条件,也不会告警),默认与 period 相同
silence-period:
# 当告警触发时发送的消息
message:
# 告警触发时的行为
hooks:
# 告警触发时,发送 Webhook 请求
webhook:
# 默认告警行为
default:
# 是否作为默认的告警行为
is-default: true
# 一个或多个 Webhook URL,告警信息将被发送到这些 URL
urls:
- http://127.0.0.1/notify/
- http://127.0.0.1/go-wechat/
- ...
# 其余告警行为
xxx:
1.5 链路追踪框架对比
| 对比 | Zipkin | Jaeger | SkyWalking | Pinpoint |
|---|---|---|---|---|
| 开发语言 | Java | Go | Java | Java |
| 特点 | 轻量级,简单 | 云原生,可扩展 | 国产,多语言支持,UI 强大 | 功能强大,多插件 |
| 支持语言 | Java, Go, Node.js 等 | Java, Node.js, Go, Python 等 | Java, .NET Core, Node.js, PHP, Go 等 | Java, PHP |
| 数据存储 | Cassandra, Elasticsearch, MySQL | Cassandra, Elasticsearch | Elasticsearch, MySQL, H2 等 | HBase |
| UI | 功能简单,直观 | 功能丰富,高级查询 | 功能丰富,服务拓扑,依赖分析 | 功能强大,详细调用链分析 |
| 性能 | 相对较好 | 性能较好,注重减少性能影响 | 基于 Agent,性能损耗低 | 字节码注入,性能影响 |
| 扩展性 | 插件系统简单 | 易于扩展 | 支持自定义插件,灵活 | 插件支持,不如 Agent灵活 |
2、快速入门
2.1 快速启动 SkyWalking
1)官网下载并解压压缩包
- SkyWalking APM:是一个开源的、分布式追踪系统,用于监控微服务、云原生和容器化应用程序的性能。
- Java Agent:是 SkyWalking 的一部分,它作为一个 Java 字节码增强器,用于在运行时自动收集 Java 应用程序的性能数据。
2)配置
- 在 config 目录下,根据需要修改 application.yml(OAP 核心配置文件)和其他相关配置文件。
3)启动
- 在 bin 目录下,执行启动脚本启动 SkyWalking。(Linux:
./startup.sh、Windows:./startup.bat)
4)Agent
- 在 Java Agent 中修改 agent.config,设置服务名称和 OAP 服务地址等信息。
- 在启动 Java 服务时,在 JVM 参数中添加
-javaagent Java Agent 的路径。
5)访问 UI
- 在浏览器中输入 http://SkyWalking_IP:SkyWalking_Port,查看监控数据和分析结果。
2.2 日志收集
SkyWalking 通过其日志分析器(log-analyzer)支持日志收集。该分析器能够从各种来源收集日志,并将其与 追踪和度量数据相关联,从而提供更全面的服务性能视图。
实现日志收集的步骤:(以 Logback 为例)
- 引入依赖。
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm‐toolkit‐logback‐1.x</artifactId>
<version>x.x.x</version>
</dependency>
- 配置日志框架。
<?xml version="1.0" encoding="UTF‐8"?>
<configuration>
<!‐‐ 引入 Spring Boot 默认的 logback XML 配置文件 ‐‐>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="console" 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>${CONSOLE_LOG_PATTERN}</Pattern>
</layout>
</encoder>
</appender>
<!‐‐ 设置 Appender ‐‐>
<root level="INFO">
<appender‐ref ref="console"/>
</root>
</configuration>
2.3 告警通知
SkyWalking 的告警系统允许用户定义告警规则,并在满足特定条件时触发告警。以下是告警通知功能使用步骤:
- 告警规则配置:通过 alarm-settings.yml 文件,用户可以定义告警规则,包括告警表达式(expression)、周期(period)、静默周期(silence-period)和告警消息(message)。
- 告警通知:触发的告警可以通过多种方式通知用户,如 Webhook、邮件、消息队列等。
案例:当服务的平均响应时间超过 1000ms(即 1s)时,通过 Webhook 将告警信息发送到飞书。
rules:
service_resp_time_rule:
expression: avg(service_resp_time) > 1000
period: 10
silence-period: 5
message: '{name} 的平均响应时间超过1秒,持续了 {period} 分钟。'
hooks:
feishu:
default:
is-default: true
text-template: |-
{
"msg_type": "text",
"content": {
"text": "Apache SkyWalking Alarm: \n %s."
},
"ats":"feishu_user_id_1,feishu_user_id_2"
}
webhooks:
# 飞书机器人的 Webhook URL
- url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
# 飞书机器人的签名验证
secret: dummysecret
2.4 自定义 SkyWalking 链路追踪
自定义 SkyWalking 链路追踪是指根据特定的业务需求,通过编程方式扩展或修改 SkyWalking 的默认链路追踪行为。以下是具体流程:
1)引入依赖
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.9.0</version>
</dependency>
2)自定义链路追踪
- 使用 @Trace 标记需要追踪的方法。
- 使用 @Tag 添加自定义标签,以存储额外的信息。
public class UserService {
@Trace
@Tag(key="userId", value="arg[0])
@Tag(key="result.name", value="returnedObj.name)
public User getUser(long userId) {
// ...
}
}
3、SkyWalking 原理
3.1 数据结构
1)Trace:表示一个完整的请求链路,涵盖分布式系统中所有相关组件的调用关系和性能信息。每个Trace都有一个全局唯一的ID,用于标识整个请求链路。
2)TraceSegment:是组成 Trace 的基本单元,一个 Trace 可以包含多个 TraceSegment,一个 TraceSegment 包含多个 Span。
3)Span:一次操作的单位,通常对应于分布式事务中的一个逻辑工作单元。
在 SkyWalking 中,根据 Span 的作用和上下文,可以将它们分为以下三种类型:
- Entry Span:服务的入口点,通常是处理客户端请求的开始。
- Local Span:在单个服务实例内部的操作,不涉及跨网络的远程调用。
- Exit Span:从本服务调用另一个服务的请求,通常是服务间的远程调用。
3.2 Java Agent
1)Java Agent 的入口
public static void premain(String agentArgs, Instrumentation instrumentation) {
final PluginFinder pluginFinder;
// 1. 初始化核心配置。
try {
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
} catch (Exception e) {
return;
}
// 2. 检查 SkyWalking Agent 是否被启用。
if (!Config.Agent.ENABLE) {
return;
}
// 3. 创建一个 PluginFinder 实例,加载所有插件。
try {
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
} catch (AgentPackageNotFoundException ape) {
return;
} catch (Exception e) {
return;
}
// 4. 注册类转换器,以便在类加载时进行字节码增强。
try {
installClassTransformer(instrumentation, pluginFinder);
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent installed class transformer failure.");
}
// 5. 启动 SkyWalking Agent 的内部服务。
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
LOGGER.error(e, "Skywalking agent boot failure.");
}
// 6. 在 JVM 关闭时注册一个关闭钩子,优雅地关闭 SkyWalking Agent 的内部服务。
Runtime.getRuntime()
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}
2)Java Agent Plugin
在 SkyWalking 中,Java Agent 是通过 Plugin(插件)来实现具体组件的链路追踪。如:
- HttpClient:httpclient-4.x-plugin、httpclient-5.x-plugin
- MongoDB:mongodb-3.x-plugin、mongodb-4.x-plugin
- MySQL:mysql-5.x-plugin、mysql-8.x-plugin
- Tomcat:tomcat-10x-plugin
- ...
以 Tomcat 为例:
public class TomcatInvokeInterceptor implements InstanceMethodsAroundInterceptor {
/**
* 在目标方法执行之前调用,用于初始化追踪 Span。
*/
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, MethodInterceptResult result) {
// 1. 从 Request 对象的头部中提取上下文信息,并设置到 ContextCarrier 中
Request request = (Request) allArguments[0];
ContextCarrier contextCarrier = new ContextCarrier();
CarrierItem next = contextCarrier.items();
while (next.hasNext()) {
next = next.next();
next.setHeadValue(request.getHeader(next.getHeadKey()));
}
// 2. 根据请求的方法和请求 URI 构造操作名称
String operationName = String.join(":", request.getMethod(), request.getRequestURI());
// 3. 构造一个新的 Entry Span,并将 Span 的组件设置为 Tomcat
AbstractSpan span = ContextManager.createEntrySpan(operationName, contextCarrier);
Tags.URL.set(span, request.getRequestURL().toString());
Tags.HTTP.METHOD.set(span, request.getMethod());
span.setComponent(ComponentsDefine.TOMCAT);
SpanLayer.asHttp(span);
if (TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS) {
collectHttpParam(request, span);
}
}
/**
* 在目标方法执行之后调用,用于处理 Span 的结束逻辑。
*/
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) {
// 1. 从上下文管理器中获取当前的 Span,为 Span 设置 HTTP 响应状态码标签
Request request = (Request) allArguments[0];
HttpServletResponse response = (HttpServletResponse) allArguments[1];
AbstractSpan span = ContextManager.activeSpan();
Tags.HTTP_RESPONSE_STATUS_CODE.set(span, response.getStatus());
// 2. 如果响应状态码大于或等于 400,标记 Span 为发生错误的 Span
if (response.getStatus() >= 400) {
span.errorOccurred();
}
// 3. 移除与请求相关的特定上下文信息,停止并结束当前的 Span
if (!TomcatPluginConfig.Plugin.Tomcat.COLLECT_HTTP_PARAMS && span.isProfiling()) {
collectHttpParam(request, span);
}
ContextManager.getRuntimeContext().remove(Constants.FORWARD_REQUEST_FLAG);
ContextManager.stopSpan();
// 4. 返回原始方法的返回值
return ret;
}
}