告别分布式盲调:SkyWalking 底层原理与全链路故障定位全解析

0 阅读29分钟

微服务架构的普及,让一次用户请求可能跨越前端网关、业务服务、数据库、缓存、消息队列等数十个节点。传统的单点监控只能看到单个服务的运行状态,无法串联起完整的请求链路,一旦出现接口超时、服务异常、数据不一致等问题,研发人员只能在各个服务的日志中大海捞针,排查效率极低。全链路追踪技术正是为了解决这一痛点而生,而Apache SkyWalking作为国产开源的顶级APM(应用性能监控)产品,凭借无侵入接入、高性能、多语言支持、全场景覆盖的优势,成为了国内企业分布式可观测性建设的首选方案。

本文将从全链路追踪的核心理论出发,逐层拆解SkyWalking的底层实现原理,结合可落地的业务实战案例,手把手教你掌握分布式故障的定位方法,让你彻底吃透SkyWalking,告别分布式系统的盲调困境。

一、全链路追踪的核心理论基础

要理解SkyWalking,首先要掌握全链路追踪的核心模型与规范,这是所有追踪系统的底层基石。

1.1 核心概念定义

全链路追踪的核心思想来自Google Dapper论文,其核心是通过全局唯一的标识,将一次分布式请求中分散在各个服务节点的调用信息串联起来,还原出完整的调用链路。核心概念如下:

  • Trace:一次完整的分布式请求,从用户发起请求到收到响应的全流程,对应唯一的TraceID。TraceID是整个链路的唯一标识,贯穿请求的全生命周期,就像快递的单号,通过单号就能查到快递的全流程中转信息。
  • Span:链路中的最小执行单元,对应一次具体的操作,比如HTTP请求、RPC调用、数据库操作、本地方法执行。每个Span都有唯一的SpanID,同时记录了操作的开始时间、结束时间、操作名称、标签、事件日志等信息。
  • Span上下文:包含TraceID、SpanID、ParentSpanID(父SpanID)等核心信息,是实现链路串联的关键。父Span与子Span形成树形结构,完整还原请求的调用关系。
  • Tag:Span的属性标签,以键值对的形式记录操作的附加信息,比如HTTP请求的url、状态码、数据库操作的SQL语句、RPC调用的接口名等,用于过滤和定位问题。
  • Log:Span内的事件日志,通常用于记录操作过程中的异常信息、关键事件,比如异常堆栈、业务关键节点日志,是定位问题的核心依据。

1.2 OpenTracing规范

OpenTracing是一套与平台无关、语言无关的全链路追踪规范,定义了统一的API和数据模型,解决了不同追踪系统之间的兼容性问题。SkyWalking完全兼容OpenTracing规范,同时针对分布式场景做了大量优化,核心是实现了上下文的跨进程、跨线程传播,保证链路的完整性。

1.3 链路串联的核心逻辑

一次分布式请求的链路串联,本质是上下文的持续传递:

  1. 请求进入第一个服务时,生成全局唯一的TraceID,创建入口Span,生成SpanID,ParentSpanID为空。
  2. 服务内发起本地方法调用时,创建子Span,ParentSpanID指向父Span的SpanID。
  3. 服务发起跨进程调用时,将TraceID、当前SpanID等上下文信息注入到请求头中,传递给下游服务。
  4. 下游服务接收到请求后,从请求头中解析出上下文,创建子Span,ParentSpanID指向上游服务的SpanID,继续传递上下文。
  5. 所有Span执行完成后,通过TraceID将所有Span聚合,还原出完整的树形调用链路。

二、SkyWalking 整体架构解析

SkyWalking采用分层架构设计,各模块职责清晰,解耦性强,支持水平扩展,可满足从个人测试到企业级大规模集群的部署需求。整体架构分为四大核心模块,架构图如下:

2.1 Agent探针模块

Agent是SkyWalking的数据采集端,以无侵入的方式挂载到业务应用中,负责采集链路追踪、JVM指标、服务实例信息等数据,经过预处理后上报给OAP Server。SkyWalking支持Java、Go、Python、C#等十多种语言的Agent,其中Java Agent功能最完善,性能最优,也是本文的核心讲解对象。

2.2 OAP Server模块

OAP(Observability Analysis Platform)可观测性分析平台,是SkyWalking的核心大脑,负责数据的接收、清洗、分析、聚合、存储与查询。核心分为两大核心流程:

  • 流式处理流程:实时接收Agent上报的原始数据,解析链路模型,生成服务拓扑、实时指标、调用关系,写入存储。
  • 批处理流程:按时间维度对历史数据进行聚合,生成小时级、天级的统计指标,用于长期趋势分析与容量规划。

OAP支持单机与集群两种部署模式,集群模式下通过注册中心(ZooKeeper/Nacos)实现服务发现与负载均衡,可水平扩展支撑数万服务实例的监控需求。

2.3 存储模块

存储模块负责SkyWalking所有数据的持久化,支持多种存储引擎,不同存储引擎的适用场景如下:

  • Elasticsearch:生产环境首选,支持全文检索,高性能的读写能力,适合链路数据、日志数据的存储与查询,支持大规模集群部署。
  • MySQL/PostgreSQL:适合测试环境、小规模部署场景,部署简单,运维成本低。
  • TiDB:分布式关系型数据库,适合需要强事务、水平扩展的大规模部署场景。
  • InfluxDB/Prometheus:时序数据库,适合指标数据的存储与分析,不适合链路明细数据的存储。

2.4 UI可视化模块

SkyWalking UI是基于React开发的可视化界面,提供了服务拓扑图、全链路追踪、指标监控、告警管理、日志分析等功能,通过GraphQL协议与OAP Server交互,直观展示分布式系统的运行状态,是研发人员排查问题的核心入口。

三、SkyWalking 核心底层原理深度拆解

这一部分是本文的核心,将逐层拆解SkyWalking的底层实现逻辑,用通俗的语言讲透无侵入采集、链路传播、数据处理的核心原理,让你不仅知其然,更知其所以然。

3.1 Java Agent无侵入采集原理

SkyWalking Java Agent的核心是基于JVM提供的Instrumentation API实现的字节码增强,无需修改任何业务代码,即可实现链路数据的采集,这也是其无侵入特性的核心来源。

3.1.1 Java Agent核心机制

JVM提供了两种Agent加载方式,SkyWalking均提供支持:

  • premain模式:启动时加载,在应用主方法执行之前,通过-javaagent参数挂载Agent,JVM会优先执行Agent的premain方法,完成字节码增强的初始化。这是SkyWalking的默认加载方式,稳定性最高,适合生产环境。
  • agentmain模式:运行时动态加载,通过JVM的Attach API,在应用运行过程中动态挂载Agent,无需重启应用。适合无法重启的生产服务紧急接入场景,稳定性略低于premain模式。

Instrumentation API提供了ClassFileTransformer接口,允许Agent在类加载的时候,拦截并修改类的字节码,SkyWalking正是通过这个接口,在目标类加载时,插入链路追踪的逻辑,实现无侵入的采集。

3.1.2 字节码增强实现:ByteBuddy

SkyWalking没有直接使用底层的ASM字节码操作框架,而是选择了ByteBuddy作为字节码增强的核心库。ByteBuddy提供了更易用的流式API,屏蔽了字节码的底层细节,开发效率更高,同时生成的字节码经过了优化,性能远超ASM的手写代码,也避免了手写字节码带来的稳定性问题。

SkyWalking字节码增强的完整流程如下:

3.1.3 插件化架构设计

SkyWalking的所有框架支持能力,都是通过插件实现的,核心框架只提供了字节码增强的内核与链路追踪的核心模型,所有的SpringMVC、Dubbo、MyBatis、Redis等框架的支持,都是以独立插件的形式存在,放在plugins目录下,Agent启动时自动加载。

这种插件化架构的优势非常明显:

  • 按需加载:不需要的插件可以直接从plugins目录删除,减少字节码增强的范围,降低性能损耗。
  • 易于扩展:用户可以根据业务需求,开发自定义插件,支持自研框架的链路追踪。
  • 解耦性强:插件的升级与修改不影响核心框架,稳定性更高。

每个插件的核心是定义了两个部分:

  • 类与方法匹配规则:定义需要增强的类名、方法名、方法参数,比如匹配org.springframework.web.servlet.DispatcherServlet的doDispatch方法。
  • 拦截器逻辑:定义方法执行前、执行后、异常时的处理逻辑,比如方法执行前创建Span,执行后结束Span,异常时记录异常日志。

3.2 全链路上下文传播原理

链路能跨服务、跨线程串联起来,核心是上下文的正确传播。SkyWalking的上下文传播分为跨线程传播与跨进程传播两大场景,分别解决了服务内异步调用与服务间分布式调用的链路串联问题。

3.2.1 上下文核心模型

SkyWalking的上下文核心是ContextCarrier与ContextSnapshot两个类:

  • ContextCarrier:用于跨进程的上下文传播,负责将Trace上下文序列化后注入到请求头中,在下游服务中反序列化还原上下文。
  • ContextSnapshot:用于跨线程的上下文传播,负责在父线程中捕获当前的Trace上下文,在子线程中还原,实现异步场景的链路串联。

上下文的核心数据包括:TraceID、TraceSegmentID、SpanID、服务名、服务实例名、端点名、采样标记等,这些数据是链路串联的核心。

3.2.2 跨线程上下文传播

在Java应用中,大量使用了线程池、CompletableFuture等异步编程方式,默认情况下,ThreadLocal中的上下文无法传递到子线程中,会导致异步场景的链路断裂。

SkyWalking的解决方案是:通过字节码增强,包装了所有的线程池、Runnable、Callable、CompletableFuture等异步组件,在父线程提交任务时,捕获当前的ContextSnapshot,绑定到任务中;在子线程执行任务前,将ContextSnapshot中的上下文还原到子线程的ThreadLocal中,任务执行完成后清除上下文,避免上下文污染。

这种方式完美解决了异步场景的链路传播问题,无需修改业务代码,即可实现异步方法的链路追踪。

3.2.3 跨进程上下文传播

跨进程传播是分布式链路串联的核心,SkyWalking通过在请求协议中注入上下文头,实现TraceID的跨服务传递。针对不同的通信协议,SkyWalking提供了对应的插件,自动完成上下文的注入与解析:

  • HTTP协议:通过HTTP请求头注入sw8上下文头,下游服务的SpringMVC插件自动从请求头中解析sw8,还原上下文。
  • Dubbo RPC协议:通过Dubbo的Attachment传递上下文,Provider端自动从Attachment中解析上下文。
  • MQ协议:通过消息的Properties传递上下文,消费端自动从Properties中解析上下文。
  • 数据库协议:通过SQL注释传递上下文,实现数据库访问的链路串联。

其中sw8头是SkyWalking跨进程传播的标准格式,结构如下:

<version>-<trace-id>-<parent-segment-id>-<parent-span-id>-<parent-service>-<parent-service-instance>-<parent-endpoint>-<sampled>

所有字段均经过Base64编码,避免特殊字符导致的传输问题,保证跨网络、跨协议的传输兼容性。

3.3 Trace核心模型:Segment与Span

SkyWalking在OpenTracing规范的基础上,针对分布式场景做了优化,提出了Trace Segment的概念,形成了Trace -> Segment -> Span的三级模型,解决了纯Span模型在分布式场景下的性能与扩展性问题。

3.3.1 三级模型详解

  • Trace:一次完整的分布式请求,由多个Trace Segment组成,通过全局唯一的TraceID关联。
  • Trace Segment:一次请求在单个JVM进程内的链路片段,每个服务实例处理一次请求,会生成一个唯一的Trace Segment,对应唯一的SegmentID。Segment内包含了本次请求在该服务内的所有Span,以及上游服务的上下文信息。
  • Span:链路的最小执行单元,对应一次具体的操作,每个Span都属于一个Trace Segment,通过SpanID唯一标识,通过ParentSpanID形成父子关系。

这种三级模型的优势在于:

  • 分布式场景下,每个服务独立生成Segment,无需等待下游服务的返回,即可上报数据,降低了Agent的内存占用,提升了上报效率。
  • 数据聚合时,OAP只需通过TraceID将多个Segment聚合,即可还原完整链路,无需处理海量Span的父子关系,提升了分析效率。
  • Segment内包含了服务、实例、端点的完整信息,无需在每个Span中重复存储,降低了数据存储量。

3.3.2 Span的三大类型与使用场景

SkyWalking将Span分为三大类型,不同类型的Span对应不同的使用场景,这是很多用户容易混淆的核心知识点,这里做明确区分:

Span类型核心定义典型使用场景核心特点
EntrySpan服务的入站请求Span,是链路在当前服务的入口HTTP请求接收、Dubbo Provider接口调用、MQ消息消费一个Trace Segment中最多有一个EntrySpan,代表请求进入当前服务的起点
ExitSpan服务的出站请求Span,是链路从当前服务流出的节点HTTP远程调用、Dubbo Consumer接口调用、数据库操作、Redis访问、MQ消息发送对应一次跨进程/跨组件的出站操作,会注入上下文到请求中,传递给下游
LocalSpan服务内的本地操作Span,不涉及跨进程通信业务层本地方法调用、工具类方法执行、本地缓存操作用于追踪服务内部的方法执行情况,不涉及上下文的传递

3.3.3 Span的生命周期

每个Span都有严格的生命周期,只有正确完成生命周期的Span,才会被采集并上报到OAP Server:

  1. 创建阶段:通过Tracer创建Span,设置Span类型、操作名称、开始时间,绑定到当前的Trace上下文。
  2. 数据记录阶段:在方法执行过程中,通过Tag记录操作的属性信息,通过Log记录事件与异常信息。
  3. 结束阶段:方法执行完成后,调用Span的end方法,设置结束时间,计算耗时,将Span添加到当前的Trace Segment中。
  4. 上报阶段:当前Segment的所有Span都执行完成后,Agent将Segment序列化为二进制数据,通过gRPC协议上报到OAP Server。

3.4 OAP Server核心处理原理

OAP Server是SkyWalking的大脑,负责处理Agent上报的海量数据,生成可查询的链路、指标、拓扑数据。其核心处理流程分为流式处理与批处理两大核心模块。

3.4.1 流式处理流程

流式处理是OAP的核心实时处理流程,基于LMAX Disruptor高性能内存队列实现,处理延迟在毫秒级,核心流程如下:

  1. 数据接收:Receiver模块通过gRPC服务接收Agent上报的Segment数据,进行格式校验与解析。
  2. 链路分析:解析Segment中的Span数据,提取服务、实例、端点、调用关系信息,生成链路明细数据。
  3. 拓扑生成:根据Span的上下游调用关系,生成服务之间的拓扑依赖关系,更新服务拓扑图。
  4. 指标聚合:按分钟维度,聚合服务、实例、端点的调用量、响应时间、成功率、异常率等核心指标。
  5. 数据写入:将链路明细数据、拓扑数据、分钟级指标数据写入存储引擎。

3.4.2 批处理流程

批处理流程用于处理历史数据的聚合,生成长期的统计指标,核心是按小时、天维度,对分钟级指标进行二次聚合,生成小时级、天级的指标数据,用于长期趋势分析、容量规划、报表统计。批处理流程通过定时任务触发,默认每小时执行一次小时级聚合,每天执行一次天级聚合。

3.4.3 采样机制

SkyWalking的采样机制在Agent端实现,核心是为了在大规模部署场景下,减少数据上报量,降低OAP与存储的压力。采样规则如下:

  • 采样决策在Trace的入口处做出,一旦Trace被标记为采样,整个链路的所有Span都会被完整采集,不会出现半条链路的情况。
  • 采样决策基于TraceID的哈希值实现,保证同一个Trace在所有服务中的采样决策一致,避免链路断裂。
  • 支持自定义采样规则,比如按服务名、端点名、响应时间、异常状态设置不同的采样率,比如异常请求100%采样,正常请求按10%采样。

四、环境部署与业务接入实战

这一部分将带你从零开始,完成SkyWalking的部署与业务系统的接入,所有组件均采用最新稳定版本,步骤清晰,可直接落地。

4.1 环境准备

  • 操作系统:Windows/Linux/macOS
  • JDK:17+
  • MySQL:8.0+
  • Docker(可选,推荐)

4.2 SkyWalking OAP Server部署

这里提供两种部署方式,Docker部署(推荐)与二进制包部署。

4.2.1 Docker部署(推荐)

  1. 先创建MySQL数据库,用于存储SkyWalking的元数据与指标数据:
CREATE DATABASE skywalking DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'skywalking'@'%' IDENTIFIED BY 'Skywalking@123';
GRANT ALL PRIVILEGES ON skywalking.* TO 'skywalking'@'%';
FLUSH PRIVILEGES;

2. 启动OAP Server容器,配置MySQL存储:

docker run -d \
--name skywalking-oap \
-p 11800:11800 \
-p 12800:12800 \
-e SW_STORAGE=mysql \
-e SW_JDBC_URL=jdbc:mysql://你的MySQL地址:3306/skywalking?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true \
-e SW_DATA_SOURCE_USER=skywalking \
-e SW_DATA_SOURCE_PASSWORD=Skywalking@123 \
apache/skywalking-oap-server:10.3.0

3. 启动SkyWalking UI容器:

docker run -d \
--name skywalking-ui \
-p 8080:8080 \
-e SW_OAP_ADDRESS=http://skywalking-oap:12800 \
--link skywalking-oap:skywalking-oap \
apache/skywalking-ui:10.3.0

部署完成后,访问http://127.0.0.1:8080,即可进入SkyWalking UI界面,默认用户名/密码:admin/admin。

4.2.2 二进制包部署

  1. 下载Apache SkyWalking 10.3.0二进制包,解压到本地目录。
  2. 修改config/application.yml文件,将存储配置改为MySQL:
storage:
  selector: ${SW_STORAGE:mysql}
  mysql:
    properties:
      jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://127.0.0.1:3306/skywalking?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"}
      dataSource.user: ${SW_DATA_SOURCE_USER:skywalking}
      dataSource.password: ${SW_DATA_SOURCE_PASSWORD:Skywalking@123}

3. 启动OAP Server:bin/oapService.sh(Linux)/bin/oapService.bat(Windows) 4. 启动UI:bin/webappService.sh(Linux)/bin/webappService.bat(Windows)

4.3 业务系统接入实战

我们将创建两个Spring Boot服务:用户服务(user-service)与订单服务(order-service),订单服务通过OpenFeign调用用户服务,演示跨服务的全链路追踪,同时集成MyBatisPlus实现数据库操作,集成Swagger3实现接口文档。

4.3.1 父工程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.3.0</version>
        <relativePath/>
    </parent>
    <groupId>com.jam.demo</groupId>
    <artifactId>skywalking-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>user-service</module>
        <module>order-service</module>
    </modules>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.2</spring-cloud.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mysql.version>8.0.39</mysql.version>
        <lombok.version>1.18.30</lombok.version>
        <fastjson2.version>2.0.52</fastjson2.version>
        <guava.version>33.2.0-jre</guava.version>
        <springdoc.version>2.5.0</springdoc.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.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson2.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${springdoc.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.3.2 用户服务(user-service)

  1. 子模块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>com.jam.demo</groupId>
        <artifactId>skywalking-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>user-service</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </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>

2. 配置文件application.yml

server:
  port: 8081
spring:
  application:
    name: user-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/skywalking_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

3. 数据库建表语句

CREATE DATABASE skywalking_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE skywalking_demo;
CREATE TABLE t_user (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(64NOT NULL COMMENT '用户名',
    phone VARCHAR(11NOT NULL COMMENT '手机号',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除 0-未删除 1-已删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
INSERT INTO t_user (username, phone) VALUES ('zhangsan''13800138000'), ('lisi''13900139000');

4. 实体类User.java

package com.jam.demo.user.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * 用户实体类
 * @author ken
 */
@Data
@TableName("t_user")
@Schema(description = "用户实体")
public class User {
    @TableId(type = IdType.AUTO)
    @Schema(description = "用户ID", example = "1")
    private Long id;
    @Schema(description = "用户名", example = "zhangsan")
    private String username;
    @Schema(description = "手机号", example = "13800138000")
    private String phone;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @TableLogic
    @Schema(description = "是否删除")
    private Integer deleted;
}

5. Mapper接口UserMapper.java

package com.jam.demo.user.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.user.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
 * 用户Mapper接口
 * @author ken
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

6. Service层接口与实现 UserService.java

package com.jam.demo.user.service;
import com.jam.demo.user.entity.User;
/**
 * 用户服务接口
 * @author ken
 */
public interface UserService {
    /**
     * 根据用户ID查询用户信息
     * @param id 用户ID
     * @return 用户实体
     */
    User getUserById(Long id);
}

UserServiceImpl.java

package com.jam.demo.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.user.entity.User;
import com.jam.demo.user.mapper.UserMapper;
import com.jam.demo.user.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
 * 用户服务实现类
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    private final UserMapper userMapper;
    @Override
    public User getUserById(Long id) {
        if (ObjectUtils.isEmpty(id)) {
            log.warn("用户ID为空");
            return null;
        }
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getId, id)
                .eq(User::getDeleted, 0);
        User user = userMapper.selectOne(queryWrapper);
        if (ObjectUtils.isEmpty(user)) {
            log.info("用户不存在,ID:{}", id);
        }
        return user;
    }
}

7. Controller层UserController.java

package com.jam.demo.user.controller;
import com.jam.demo.user.entity.User;
import com.jam.demo.user.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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 用户控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户信息查询接口")
public class UserController {
    private final UserService userService;
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
    public User getUserById(
            @Parameter(description = "用户ID", required = true, example = "1")
            @PathVariable Long id) {
        log.info("查询用户信息,ID:{}", id);
        return userService.getUserById(id);
    }
}

8. 启动类UserServiceApplication.java

package com.jam.demo.user;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
 * 用户服务启动类
 * @author ken
 */
@SpringBootApplication
@MapperScan("com.jam.demo.user.mapper")
@EnableFeignClients
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

4.3.3 订单服务(order-service)

  1. 子模块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>com.jam.demo</groupId>
        <artifactId>skywalking-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>order-service</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </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>

2. 配置文件application.yml

server:
  port: 8082
spring:
  application:
    name: order-service
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    urljdbc:mysql://127.0.0.1:3306/skywalking_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root
springdoc:
  swagger-ui:
    path: /swagger-ui.html
  api-docs:
    path: /v3/api-docs
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value1
      logic-not-delete-value0

3. 数据库建表语句

USE skywalking_demo;
CREATE TABLE t_order (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID',
    order_no VARCHAR(64NOT NULL COMMENT '订单编号',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    amount DECIMAL(10,2NOT NULL COMMENT '订单金额',
    status TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态 0-待支付 1-已支付 2-已取消',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除 0-未删除 1-已删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
INSERT INTO t_order (order_no, user_id, amount, status) VALUES ('ORDER20240520001'199.991), ('ORDER20240520002'2199.990);

4. Feign客户端UserFeignClient.java

package com.jam.demo.order.feign;
import com.jam.demo.order.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 = "127.0.0.1:8081")
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    User getUserById(@PathVariable("id") Long id);
}

5. 实体类 User.java(用于接收Feign返回的用户数据)

package com.jam.demo.order.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
 * 用户实体类
 * @author ken
 */
@Data
@Schema(description = "用户实体")
public class User {
    @Schema(description = "用户ID", example = "1")
    private Long id;
    @Schema(description = "用户名", example = "zhangsan")
    private String username;
    @Schema(description = "手机号", example = "13800138000")
    private String phone;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @Schema(description = "是否删除")
    private Integer deleted;
}

Order.java

package com.jam.demo.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * 订单实体类
 * @author ken
 */
@Data
@TableName("t_order")
@Schema(description = "订单实体")
public class Order {
    @TableId(type = IdType.AUTO)
    @Schema(description = "订单ID", example = "1")
    private Long id;
    @Schema(description = "订单编号", example = "ORDER20240520001")
    private String orderNo;
    @Schema(description = "用户ID", example = "1")
    private Long userId;
    @Schema(description = "订单金额", example = "99.99")
    private BigDecimal amount;
    @Schema(description = "订单状态 0-待支付 1-已支付 2-已取消", example = "1")
    private Integer status;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @TableLogic
    @Schema(description = "是否删除")
    private Integer deleted;
}

6. Mapper接口OrderMapper.java

package com.jam.demo.order.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.order.entity.Order;
import org.apache.ibatis.annotations.Mapper;
/**
 * 订单Mapper接口
 * @author ken
 */
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

7. Service层接口与实现 OrderService.java

package com.jam.demo.order.service;
import com.jam.demo.order.entity.Order;
/**
 * 订单服务接口
 * @author ken
 */
public interface OrderService {
    /**
     * 根据订单ID查询订单详情,包含用户信息
     * @param id 订单ID
     * @return 订单实体
     */
    Order getOrderById(Long id);
}

OrderServiceImpl.java

package com.jam.demo.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.order.entity.Order;
import com.jam.demo.order.entity.User;
import com.jam.demo.order.feign.UserFeignClient;
import com.jam.demo.order.mapper.OrderMapper;
import com.jam.demo.order.service.OrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
 * 订单服务实现类
 * @author ken
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    private final OrderMapper orderMapper;
    private final UserFeignClient userFeignClient;
    @Override
    public Order getOrderById(Long id) {
        if (ObjectUtils.isEmpty(id)) {
            log.warn("订单ID为空");
            return null;
        }
        LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<Order>()
                .eq(Order::getId, id)
                .eq(Order::getDeleted, 0);
        Order order = orderMapper.selectOne(queryWrapper);
        if (ObjectUtils.isEmpty(order)) {
            log.info("订单不存在,ID:{}", id);
            return null;
        }
        User user = userFeignClient.getUserById(order.getUserId());
        if (ObjectUtils.isEmpty(user)) {
            log.warn("订单对应的用户不存在,用户ID:{}", order.getUserId());
        }
        log.info("查询订单详情完成,订单号:{},用户名:{}", order.getOrderNo(), ObjectUtils.isEmpty(user) ? "未知" : user.getUsername());
        return order;
    }
}

8. Controller层OrderController.java

package com.jam.demo.order.controller;
import com.jam.demo.order.entity.Order;
import com.jam.demo.order.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 订单控制器
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/order")
@RequiredArgsConstructor
@Tag(name = "订单管理", description = "订单信息查询接口")
public class OrderController {
    private final OrderService orderService;
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询订单", description = "根据订单ID查询订单详细信息,包含关联的用户信息")
    public Order getOrderById(
            @Parameter(description = "订单ID", required = true, example = "1")
            @PathVariable Long id) {
        log.info("查询订单信息,ID:{}", id);
        return orderService.getOrderById(id);
    }
}

9. 启动类OrderServiceApplication.java

package com.jam.demo.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
 * 订单服务启动类
 * @author ken
 */
@SpringBootApplication
@MapperScan("com.jam.demo.order.mapper")
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

4.3.4 Agent接入启动

  1. 下载SkyWalking Agent 10.3.0,解压到本地目录。
  2. 启动用户服务,添加JVM参数:
-javaagent:/path/to/skywalking-agent/skywalking-agent.jar
-Dskywalking.agent.service_name=user-service
-Dskywalking.collector.backend_service=127.0.0.1:11800

3. 启动订单服务,添加JVM参数:

-javaagent:/path/to/skywalking-agent/skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=127.0.0.1:11800

4. 访问订单服务接口:http://127.0.0.1:8082/order/1,触发跨服务调用。 5. 打开SkyWalking UI,即可看到服务拓扑、链路追踪数据。

五、分布式故障定位实战方法

掌握了SkyWalking的原理与接入,核心是用它解决实际的分布式故障问题。这里总结了四大高频故障场景的定位方法,结合上面的实战项目,手把手教你快速定位问题。

5.1 慢请求定位实战

慢请求是分布式系统中最常见的问题,接口响应超时,用户体验差,传统方式很难定位到具体的慢环节,通过SkyWalking可以快速定位到慢的Span,找到根因。

定位步骤:

  1. 打开SkyWalking UI,进入「服务」菜单,选择order-service服务,查看端点的响应时间排行,找到响应时间最长的端点/order/{id}。

  2. 点击端点,进入「追踪」菜单,筛选该端点的请求,按耗时降序排序,找到耗时最长的Trace。

  3. 点击Trace,查看完整的链路瀑布图,每个Span的耗时一目了然,可以快速看到哪个环节耗时最长:

    • 如果是数据库Span耗时最长,点击Span查看Tag中的SQL语句,分析是否是慢查询,是否缺少索引。
    • 如果是Feign调用Span耗时最长,点击Span查看下游服务的端点,跳转到下游服务的链路,继续分析。
    • 如果是本地方法Span耗时最长,查看方法名,定位到具体的业务代码,分析是否有循环、锁竞争、IO阻塞等问题。

5.2 异常请求定位实战

接口报错、返回500,是研发人员经常遇到的问题,分布式场景下,不知道是哪个服务抛出的异常,通过SkyWalking可以快速定位到异常的根因。

定位步骤:

  1. 打开SkyWalking UI,进入「服务」菜单,选择order-service服务,查看「异常」指标,找到异常请求的端点。
  2. 进入「追踪」菜单,筛选状态为「异常」的Trace,找到对应的请求。
  3. 点击Trace,查看链路中的Span,标红的Span就是抛出异常的Span。
  4. 点击标红的Span,查看「日志」标签,即可看到完整的异常堆栈信息,直接定位到抛出异常的代码行,快速解决问题。

5.3 全链路压测性能瓶颈定位

在系统上线前,我们会进行压测,验证系统的性能,传统的压测工具只能看到整体的QPS、响应时间,无法定位到性能瓶颈,通过SkyWalking可以结合压测,找到全链路的性能瓶颈。

定位步骤:

  1. 使用JMeter等压测工具,对订单服务的/order/{id}接口进行压测,持续施压。
  2. 打开SkyWalking UI,进入「服务」菜单,查看order-service和user-service的核心指标:QPS、响应时间、成功率、CPU使用率、JVM内存使用率。
  3. 进入「拓扑」菜单,查看服务之间的调用关系,每个服务的QPS、响应时间、异常率,找到响应时间最长的服务节点。
  4. 进入「追踪」菜单,查看压测过程中的Trace,分析每个Span的耗时占比,找到耗时占比最高的环节,比如数据库查询、Redis访问、第三方接口调用。
  5. 针对瓶颈环节进行优化,比如给数据库加索引、优化SQL、添加缓存、异步化处理,再次压测验证优化效果。

5.4 分布式事务问题定位

跨服务的分布式事务问题,比如订单创建成功,库存扣减失败,导致数据不一致,传统方式很难排查每个服务的执行情况,通过SkyWalking的全链路追踪,可以完整看到事务的每个环节的执行情况。

定位步骤:

  1. 拿到异常的订单号,从业务日志中找到对应的TraceID。
  2. 打开SkyWalking UI,进入「追踪」菜单,通过TraceID搜索到完整的链路。
  3. 查看链路中的每个Span,对应分布式事务的每个环节,比如订单创建、库存扣减、积分增加,查看每个Span的执行状态、耗时、异常信息。
  4. 找到执行失败的Span,查看异常日志,定位到事务回滚的原因,比如数据库锁等待超时、库存不足、网络超时等。
  5. 结合链路的执行顺序,分析分布式事务的一致性问题,比如是否是异步调用导致的时序问题,是否是事务传播机制配置错误等。

六、最佳实践与避坑指南

6.1 生产环境最佳实践

  1. 版本一致性:Agent版本必须与OAP Server版本完全一致,否则会出现数据不上报、解析失败的问题,这是最常见的踩坑点。
  2. 插件按需加载:plugins目录下的插件,只保留业务系统用到的插件,不需要的插件直接删除,减少字节码增强的范围,降低性能损耗,避免不必要的类冲突。
  3. 采样率合理配置:大规模部署场景下,不要使用全采样,根据业务需求配置合理的采样率,比如核心接口100%采样,非核心接口10%采样,异常请求100%采样,减少数据存储量,提升查询效率。
  4. OAP集群部署:生产环境必须使用集群模式部署OAP Server,通过负载均衡实现高可用,避免单点故障,同时根据数据量水平扩展OAP节点。
  5. 存储引擎选择:生产环境优先选择Elasticsearch作为存储引擎,配置合理的分片数与副本数,设置数据过期时间,避免存储无限增长,定期清理历史数据。
  6. 告警配置:配置合理的告警规则,比如接口响应超时、异常率突增、服务不可用、JVM内存溢出等,及时发现问题,提前预警。

6.2 常见踩坑与解决方案

  1. Agent启动后,服务没有出现在SkyWalking UI中

    • 原因:Agent与OAP版本不一致;OAP地址配置错误;防火墙拦截了11800端口;服务没有收到请求,没有生成Trace数据。
    • 解决方案:核对Agent与OAP版本;检查backend_service配置是否正确;检查防火墙端口是否开放;调用服务接口,触发Trace生成。
  2. 跨服务调用,TraceID不传递,链路断裂

    • 原因:使用了自定义的HTTP客户端,没有对应的SkyWalking插件;跨线程调用,没有传递上下文;请求头被网关过滤,sw8头被丢弃。
    • 解决方案:添加对应HTTP客户端的插件;使用SkyWalking支持的异步组件,或者手动传递上下文;配置网关,放行sw8开头的请求头。
  3. 异步方法调用,链路丢失

    • 原因:使用了自定义的线程池,没有被SkyWalking增强;手动创建线程,没有传递上下文。
    • 解决方案:使用Spring管理的线程池,SkyWalking会自动增强;如果使用自定义线程池,需要手动包装Runnable/Callable,传递ContextSnapshot。
  4. 数据库操作,没有生成对应的Span

    • 原因:使用了自定义的数据源,没有对应的插件;MyBatisPlus的版本与SkyWalking插件版本不兼容;SQL执行没有经过JDBC标准接口。
    • 解决方案:添加对应数据源的插件;升级SkyWalking插件到最新版本,兼容MyBatisPlus;使用标准的JDBC接口操作数据库。
  5. OAP Server内存溢出,频繁重启

    • 原因:数据量过大,OAP的JVM内存配置不足;聚合规则过多,导致内存占用过高;存储引擎写入慢,导致数据堆积在OAP内存中。
    • 解决方案:调大OAP的JVM堆内存;优化采样率,减少数据量;优化聚合规则,关闭不需要的聚合;优化存储引擎的写入性能,升级Elasticsearch集群。

总结

分布式系统的可观测性,是保障系统稳定性的核心能力,而全链路追踪是可观测性体系的核心支柱。SkyWalking凭借无侵入的接入方式、完善的多语言支持、高性能的数据处理能力、丰富的可视化功能,成为了分布式全链路追踪的首选方案。