SpringCloud——SkyWalking全链路监控源码深度解析

目录

[SkyWalking 源码复盘 ⑫:从一次请求到监控结果的完整源码地图](#SkyWalking 源码复盘 ⑫:从一次请求到监控结果的完整源码地图)

[一、SkyWalking 整体分层](#一、SkyWalking 整体分层)

[二、第一阶段:JVM 启动 Agent](#二、第一阶段:JVM 启动 Agent)

三、第二阶段:插件匹配与字节码增强

[四、第三阶段:HTTP 请求创建 EntrySpan](#四、第三阶段:HTTP 请求创建 EntrySpan)

[五、第四阶段:ContextManager 管理当前 Trace](#五、第四阶段:ContextManager 管理当前 Trace)

[Span 栈](#Span 栈)

[六、第五阶段:@Trace 创建业务 LocalSpan](#六、第五阶段:@Trace 创建业务 LocalSpan)

[七、第六阶段:HikariCP 与 JDBC 拆分数据库耗时](#七、第六阶段:HikariCP 与 JDBC 拆分数据库耗时)

[1. 获取连接](#1. 获取连接)

[2. 执行 SQL](#2. 执行 SQL)

[3. 归还连接](#3. 归还连接)

你的两个实验

[SSH 隧道断开](#SSH 隧道断开)

/order/slowSql?time=2

[八、第七阶段:Feign 创建 ExitSpan 并传播上下文](#八、第七阶段:Feign 创建 ExitSpan 并传播上下文)

[九、第八阶段:TraceSegment 异步上报](#九、第八阶段:TraceSegment 异步上报)

缓冲区满了怎么办

[十、OAP 接收并解析 Segment](#十、OAP 接收并解析 Segment)

[十一、为什么一条 Trace 能产生仪表盘指标](#十一、为什么一条 Trace 能产生仪表盘指标)

十二、拓扑图是如何形成的

[十三、原始 Trace 与 Metrics 是两条不同链路](#十三、原始 Trace 与 Metrics 是两条不同链路)

十四、日志关联链路

[1. %tid 输出 Trace ID](#1. %tid 输出 Trace ID)

[2. GRPCLogClientAppender 上传日志](#2. GRPCLogClientAppender 上传日志)

[控制台有 TID,但 UI 没日志](#控制台有 TID,但 UI 没日志)

十五、告警链路

[为什么 WebHook 是 JSON 数组](#为什么 WebHook 是 JSON 数组)

十六、你的告警完整链路

[十七、Trace Profiling 链路](#十七、Trace Profiling 链路)

三个核心指标

[Dump Count](#Dump Count)

Duration

[Self Duration](#Self Duration)

判断方法

十八、四条核心数据链

链路一:Trace

[链路二:Metrics 与拓扑](#链路二:Metrics 与拓扑)

链路三:日志

链路四:告警

十九、故障排查的标准顺序

[1. UI 看不到服务](#1. UI 看不到服务)

[2. 服务有指标,但没有 Trace](#2. 服务有指标,但没有 Trace)

[3. Trace 断链](#3. Trace 断链)

[4. Trace 显示数据库慢](#4. Trace 显示数据库慢)

[5. 控制台有 TID,但 UI 没日志](#5. 控制台有 TID,但 UI 没日志)

[6. 告警不触发](#6. 告警不触发)

[7. Profiling 没数据](#7. Profiling 没数据)

二十、整套源码中的核心类

二十一、一段完整的面试回答

[二十二、你目前对 SkyWalking 的掌握层次](#二十二、你目前对 SkyWalking 的掌握层次)

二十三、最终记忆图


SkyWalking 源码复盘 ⑫:从一次请求到监控结果的完整源码地图

这一节把前面所有内容收束成一条完整主线。

以你的请求为例:

复制代码
GET /order/query/1

最终可能产生:

复制代码
服务监控指标
端点指标
分布式 Trace
服务拓扑
数据库调用记录
带 TID 的业务日志
日志与 Trace 关联
告警 WebHook
Trace Profiling 线程栈

这些并不是一个模块一次性完成的,而是由 Agent 采集、OAP 分析、存储、UI 查询 四个阶段共同完成。


一、SkyWalking 整体分层

复制代码
┌─────────────────────────────────────────┐
│ 业务应用                                │
│ order / account / storage / alarm       │
└─────────────────────────────────────────┘
                    │
                    │ -javaagent
                    ▼
┌─────────────────────────────────────────┐
│ Java Agent                              │
│                                         │
│ 插件匹配                                │
│ 字节码增强                              │
│ 创建 Span                               │
│ 传播 Trace Context                      │
│ 采集日志、JVM、Profiling 数据           │
│ 异步上报                                │
└─────────────────────────────────────────┘
                    │
                    │ gRPC 11800
                    ▼
┌─────────────────────────────────────────┐
│ OAP                                     │
│                                         │
│ 接收 Segment / Log / Profile Snapshot   │
│ 分析 Span                               │
│ 生成 Source                             │
│ OAL 聚合 Metrics                        │
│ 执行告警规则                            │
│ 写入存储                                │
└─────────────────────────────────────────┘
                    │
                    │ HTTP 12800
                    ▼
┌─────────────────────────────────────────┐
│ SkyWalking UI 8080                      │
│                                         │
│ 服务仪表盘                              │
│ 拓扑图                                  │
│ Trace                                   │
│ 日志                                    │
│ 告警                                    │
│ Profiling                               │
└─────────────────────────────────────────┘

端口必须记住:

端口 用途
11800 Agent 向 OAP 上报数据
12800 UI 查询 OAP
8080 SkyWalking UI
8084 你的告警接收服务

二、第一阶段:JVM 启动 Agent

你的启动参数类似:

复制代码
-javaagent:H:\tools\...\skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=127.0.0.1:11800

JVM 启动顺序:

复制代码
JVM 启动
→ 加载 skywalking-agent.jar
→ 调用 SkyWalkingAgent.premain()
→ 读取 Agent 配置
→ 扫描插件
→ 构造 Byte Buddy Transformer
→ 注册 Instrumentation
→ 启动 Agent 内部服务
→ 执行业务应用 main()

核心逻辑可概括为:

复制代码
public static void premain(
        String agentArgs,
        Instrumentation instrumentation
) {
    initializeConfig(agentArgs);
    loadPlugins();
    installClassTransformer(instrumentation);
    ServiceManager.INSTANCE.boot();
}

这里要区分:

复制代码
Maven 依赖
→ 让项目能编译和调用 Toolkit API

-javaagent
→ 真正修改目标类字节码并采集数据

所以:

复制代码
只添加 Maven 依赖
≠
已经启用 SkyWalking Agent

三、第二阶段:插件匹配与字节码增强

Agent 不会修改所有类。

它会根据插件规则匹配:

复制代码
Tomcat
Feign
JDBC
HikariCP
Logback
@Trace
线程池
......

例如:

复制代码
Tomcat 插件
→ 增强 StandardHostValve.invoke()

Feign 插件
→ 增强 HTTP Client execute()

HikariCP 插件
→ 增强 getConnection()、close()

JDBC 插件
→ 包装 PreparedStatement.execute()

Toolkit 插件
→ 增强带 @Trace 的方法

运行时可以近似理解为:

复制代码
beforeMethod();

try {
    return originalMethod();
} catch (Throwable e) {
    handleException(e);
    throw e;
} finally {
    afterMethod();
}

SkyWalking 没有修改你的业务源代码,但 JVM 实际执行的方法已经插入了追踪逻辑。


四、第三阶段:HTTP 请求创建 EntrySpan

浏览器访问:

复制代码
GET http://127.0.0.1:8083/order/query/1

进入:

复制代码
Tomcat
→ StandardHostValve.invoke()
→ TomcatInvokeInterceptor

请求前:

复制代码
ContextCarrier carrier = readHeaders(request);

AbstractSpan span =
        ContextManager.createEntrySpan(
                "GET:/order/query/1",
                carrier
        );

随后记录:

复制代码
URL
HTTP Method
Tomcat Component
HTTP Layer

形成:

复制代码
EntrySpan
└─ GET:/order/query/1

如果这是浏览器发起的首个请求:

复制代码
请求头没有有效上游上下文
→ 创建一条新的 Trace

如果是上游微服务调用:

复制代码
请求头中存在传播信息
→ 恢复上游 Trace

五、第四阶段:ContextManager 管理当前 Trace

第一次创建 Span 时:

复制代码
CONTEXT.get() == null
→ 创建 TracingContext
→ 创建 TraceSegment
→ 写入 ThreadLocal

关键结构:

复制代码
ContextManager
└─ ThreadLocal<TracingContext>
   ├─ TraceSegment
   ├─ activeSpanStack
   └─ spanIdGenerator

不同 Tomcat 工作线程:

复制代码
线程 A → TracingContext A
线程 B → TracingContext B
线程 C → TracingContext C

因此并发请求不会互相串链。


Span 栈

你的请求执行时:

复制代码
GET:/order/query/1
→ queryOrder
→ HikariCP getConnection
→ JDBC execute

对应入栈:

复制代码
Tomcat
Tomcat → queryOrder
Tomcat → queryOrder → HikariCP
Tomcat → queryOrder → JDBC

创建子 Span 时:

复制代码
AbstractSpan parent = activeSpanStack.getLast();

newSpan.parentSpanId =
        parent.getSpanId();

结束时必须严格后进先出:

复制代码
先结束 JDBC
再结束 queryOrder
最后结束 Tomcat

Span 栈为空后:

复制代码
TraceSegment 完成
→ ThreadLocal.remove()

六、第五阶段:@Trace 创建业务 LocalSpan

业务方法:

复制代码
@Trace(operationName = "queryOrder")
@Tags({
    @Tag(key = "orderId", value = "arg[0]"),
    @Tag(key = "result", value = "returnedObj")
})
public OrderInfo queryOrder(Integer orderId) {
    return orderMapper.selectById(orderId);
}

执行过程:

复制代码
进入方法
→ createLocalSpan("queryOrder")
→ 读取 arg[0]
→ 执行业务逻辑
→ 读取 returnedObj
→ stopSpan()

结果:

复制代码
GET:/order/query/1                 EntrySpan
└─ queryOrder                     LocalSpan
   ├─ orderId = 1
   └─ result = OrderInfo(...)

这里不是 Spring AOP,而是 Agent 直接增强方法字节码。


七、第六阶段:HikariCP 与 JDBC 拆分数据库耗时

数据库调用过程:

复制代码
MyBatis
→ HikariDataSource.getConnection()
→ PreparedStatement.execute()
→ Connection.close()

形成:

复制代码
queryOrder
├─ HikariCP/Connection/getConnection   LocalSpan
├─ Mysql/JDBC/PreparedStatement/execute ExitSpan
└─ HikariCP/Connection/close           LocalSpan

1. 获取连接

复制代码
HikariCP/Connection/getConnection

回答:

应用拿到数据库连接花了多久?

慢时重点排查:

复制代码
连接池耗尽
等待其他线程归还连接
数据库不可达
SSH 隧道断开
建立新连接过慢
连接泄漏

2. 执行 SQL

复制代码
Mysql/JDBC/PreparedStatement/execute

回答:

从调用 JDBC 到数据库返回,整体花了多久?

慢时重点排查:

复制代码
SQL 缺少索引
锁等待或死锁
扫描行数过多
数据库 CPU、磁盘 IO 高
网络延迟
SSH 隧道拥塞
结果集过大

3. 归还连接

复制代码
HikariCP/Connection/close

一般不是关闭物理连接,而是:

复制代码
清理连接状态
→ 归还连接池
→ 供其他请求复用

你的两个实验

SSH 隧道断开

复制代码
HikariCP/getConnection ≈ 30 秒

结论:

复制代码
SQL 尚未真正执行
主要慢在获取或创建连接

/order/slowSql?time=2

复制代码
getConnection 很短
JDBC execute ≈ 2 秒
close 很短

结论:

复制代码
连接正常
主要慢在 SQL 或数据库网络调用

八、第七阶段:Feign 创建 ExitSpan 并传播上下文

order-service 调用:

复制代码
accountApi.deduct(...);

Feign 请求发送前:

复制代码
ContextCarrier carrier =
        new ContextCarrier();

AbstractSpan span =
        ContextManager.createExitSpan(
                "/account/deduct",
                carrier,
                "127.0.0.1:8082"
        );

随后将 Carrier 中的数据写入 HTTP Header。

调用链:

复制代码
order-service
├─ EntrySpan:POST:/order/create
└─ ExitSpan:/account/deduct
          │
          │ Trace Header
          ▼
account-service
└─ EntrySpan:PUT:/account/deduct

两个服务拥有:

复制代码
相同 Trace ID
不同 Segment ID
不同 Span ID

不是共享同一个 Span 对象。


九、第八阶段:TraceSegment 异步上报

最后一个 Span 结束:

复制代码
activeSpanStack 为空
→ TraceSegment.finish()
→ 通知 TraceSegmentServiceClient

业务线程只执行:

复制代码
carrier.produce(traceSegment);

真正的处理由后台线程完成:

复制代码
DataCarrier
→ Agent 后台消费者
→ TraceSegment.transform()
→ SegmentObject
→ gRPC
→ OAP 11800

这种设计避免:

复制代码
OAP 慢
→ Tomcat 请求线程也被阻塞

缓冲区满了怎么办

默认策略偏向:

复制代码
缓冲区有空间
→ 正常放入

缓冲区满
→ 丢弃部分 Segment
→ 不长期阻塞业务线程

SkyWalking 的取舍是:

复制代码
业务稳定性
>
遥测数据绝对完整性

所以:

复制代码
接口成功
≠
这条 Trace 一定成功上报

十、OAP 接收并解析 Segment

OAP 接收路径:

复制代码
TraceSegmentReportServiceHandler
→ SegmentParserService
→ TraceAnalyzer

TraceAnalyzer 遍历:

复制代码
EntrySpan
ExitSpan
LocalSpan
Segment

并通知不同监听器:

复制代码
RPCAnalysisListener
SegmentAnalysisListener
VirtualServiceAnalysisListener
......

监听器再生成统一的 Source:

复制代码
Service
ServiceInstance
Endpoint
ServiceRelation
EndpointRelation
DatabaseAccess
Segment

随后:

复制代码
SourceReceiver
→ Dispatcher
→ OAL
→ Metrics
→ Storage

十一、为什么一条 Trace 能产生仪表盘指标

入口 Span:

复制代码
GET:/order/slowSql
Duration ≈ 2100 ms

可以生成:

复制代码
Service:order-service
ServiceInstance:order 实例
Endpoint:GET:/order/slowSql

OAL 再计算:

复制代码
service_resp_time
service_sla
service_cpm
service_percentile

service_instance_resp_time

endpoint_resp_time
endpoint_sla
endpoint_cpm
endpoint_percentile

所以:

复制代码
Trace Duration

是单次请求时间,而:

复制代码
endpoint_resp_time

是一定时间窗口内的聚合指标。


十二、拓扑图是如何形成的

拓扑并不是直接打开一条 Trace 绘制。

真实过程:

复制代码
大量 EntrySpan 和 ExitSpan
→ 生成 ServiceRelation
→ 按时间窗口聚合
→ UI 查询关系指标
→ 展示拓扑

例如:

复制代码
order-service
→ account-service
→ MySQL

分别来自:

复制代码
Feign ExitSpan / 下游 EntrySpan
JDBC Database ExitSpan

MySQL 没有安装 Java Agent,但 JDBC Span 中存在:

复制代码
peer
database type
latency
SQL statement

所以 OAP 可以创建:

复制代码
Virtual Database Service

并显示:

复制代码
order-service
      ↓
127.0.0.1:13306

十三、原始 Trace 与 Metrics 是两条不同链路

同一个 Segment 会被不同监听器处理:

复制代码
RPCAnalysisListener
→ 生成服务、端点、关系和指标

SegmentAnalysisListener
→ 根据采样策略决定是否保存原始 Trace

因此可能出现:

复制代码
服务仪表盘有数据
拓扑图有关系
但 Trace 列表中找不到某次请求

原因可能是:

复制代码
Metrics 已参与计算
但原始 Segment 因采样没有保存

这不是矛盾。


十四、日志关联链路

业务代码:

复制代码
log.info("查询订单,orderId={}", orderId);

分成两部分。


1. %tid 输出 Trace ID

复制代码
Logback Pattern
→ %tid
→ Agent 增强 PatternConverter
→ ContextManager.getGlobalTraceId()
→ 输出到控制台

例如:

复制代码
[TID:abc123] 查询订单,orderId=1

它主要方便人肉排查。


2. GRPCLogClientAppender 上传日志

Appender 将日志转换为:

复制代码
LogData
├─ service
├─ serviceInstance
├─ endpoint
├─ timestamp
├─ body
├─ level
├─ logger
├─ thread
└─ traceContext
   ├─ traceId
   ├─ segmentId
   └─ spanId

然后:

复制代码
DataCarrier
→ 后台线程
→ gRPC 11800
→ OAP Log Receiver
→ Log Analyzer
→ Storage

系统关联日志与 Trace,依靠的是结构化:

复制代码
traceId + segmentId + spanId

不是简单搜索日志字符串中的 TID。


控制台有 TID,但 UI 没日志

常见原因:

复制代码
只配置了 %tid,没有配置 gRPC Appender
Root Logger 未引用 SkyWalking Appender
服务没有挂 Agent
11800 不通
Agent 日志队列已满
OAP 日志 Receiver 异常
UI 时间或服务筛选错误

十五、告警链路

OAP 生成分钟级 Metrics 后:

复制代码
Metrics
→ RunningRule.in()
→ 每个 AlarmEntity 维护一个 Window
→ AlarmCore 定时检查
→ MQE 表达式判断
→ AlarmMessage
→ WebhookCallback

例如:

复制代码
expression: sum(endpoint_resp_time > 1000) >= 2
period: 10
silence-period: 10

含义:

复制代码
最近 10 个分钟桶中
至少有 2 个分钟桶
端点平均响应时间超过 1000 ms

所以不是:

复制代码
一条请求超过 1000 ms
→ 立即告警

而是:

复制代码
分钟级指标进入滑动窗口
→ 时间窗口满足规则
→ 告警

为什么 WebHook 是 JSON 数组

一次定时检查可能同时产生:

复制代码
端点告警
数据库告警
服务实例告警

OAP 统一保存到:

复制代码
List<AlarmMessage>

然后:

复制代码
gson.toJson(messages)

所以请求体是:

复制代码
[
  {
    "scope": "ENDPOINT",
    "name": "GET:/order/slowSql"
  },
  {
    "scope": "DATABASE",
    "name": "127.0.0.1:13306"
  }
]

你的 Controller 应使用:

复制代码
@RequestBody List<AlarmMessage> messages

十六、你的告警完整链路

复制代码
PowerShell 循环请求
→ GET /order/slowSql?time=2
→ JDBC execute 约 2 秒
→ Agent 上报 Segment
→ OAP 生成分钟级 Metrics
→ RunningRule 窗口满足条件
→ 创建 List<AlarmMessage>
→ POST alarm-service:8084
→ AlarmController
→ 邮件服务
→ QQ SMTP
→ 邮件到达

WebHook 或邮件发送失败:

复制代码
不会直接影响 order-service 请求

因为告警发生在 OAP 的独立链路中。

但 WebHook 本身仍应快速返回,邮件发送最好异步化。


十七、Trace Profiling 链路

任务创建:

复制代码
UI
→ OAP 保存 ProfileTask

Agent 定时查询:

复制代码
ProfileTaskChannelService
→ getProfileTaskCommands()

收到任务后:

复制代码
校验任务
→ 等待 Start Time
→ 启动 ProfileTaskExecutionContext

新的请求到达:

复制代码
创建 TracingContext
→ firstSpanOPName 与任务 Endpoint 匹配
→ 创建 ThreadProfiler
→ 状态 PENDING

请求运行超过:

复制代码
Min Duration Threshold

后:

复制代码
PENDING
→ PROFILING

Profiler 周期执行:

复制代码
targetThread.getStackTrace();

生成:

复制代码
taskId
segmentId
sequence
dumpTime
stack

异步上传 OAP。

OAP 再将大量采样栈合并为树。


三个核心指标

Dump Count

复制代码
该方法出现在多少次线程栈采样中

不是方法调用次数。

Duration

复制代码
该节点在连续采样区间中出现的近似总时间

不是精确方法计时。

Self Duration

复制代码
节点 Duration
-
直接子节点 Duration 之和

表示尽量排除子调用后,当前方法自身消耗的近似时间。


判断方法

复制代码
Duration 高,Self Duration 低
→ 主要慢在子调用

Duration 高,Self Duration 高
→ 当前方法自身更可能是热点

Socket read 高
→ 主要等待网络

JDBC Driver 高
→ 主要等待数据库

Thread.sleep 高
→ 线程主要处于休眠状态

十八、四条核心数据链

学完 SkyWalking 后,应该把它拆成四条链理解。

链路一:Trace

复制代码
插件拦截
→ Entry / Local / Exit Span
→ TracingContext
→ TraceSegment
→ DataCarrier
→ gRPC
→ OAP
→ Trace UI

链路二:Metrics 与拓扑

复制代码
Span
→ TraceAnalyzer
→ Source
→ OAL
→ Metrics
→ Storage
→ Dashboard / Topology

链路三:日志

复制代码
ILoggingEvent
→ %tid
→ GRPCLogClientAppender
→ LogData
→ gRPC
→ OAP Log Analyzer
→ Log UI

链路四:告警

复制代码
Metrics
→ RunningRule
→ Window
→ MQE Expression
→ AlarmMessage
→ WebHook
→ 邮件

Profiling 则是第五条专项诊断链:

复制代码
ProfileTask
→ Endpoint 匹配
→ Thread Stack Sampling
→ Snapshot
→ OAP 合并分析

十九、故障排查的标准顺序

1. UI 看不到服务

先检查:

复制代码
服务是否挂载 -javaagent
service_name 是否正确
Agent 是否启动成功
Agent 到 OAP 11800 是否连通
是否真正访问过接口
UI 时间范围是否正确

2. 服务有指标,但没有 Trace

检查:

复制代码
是否被采样丢弃
Agent Segment 缓冲区是否满
OAP Trace 存储是否正常
Trace 时间范围和服务筛选
请求是否真正经过支持的插件

3. Trace 断链

检查:

复制代码
调用方是否创建 ExitSpan
是否注入跨进程 Header
下游是否挂载 Agent
下游是否读取 Header
是否经过不支持的客户端或网关
异步线程是否传播上下文

4. Trace 显示数据库慢

先区分:

复制代码
HikariCP/getConnection 慢
还是
JDBC/execute 慢

这是你当前已经掌握的高价值诊断能力。


5. 控制台有 TID,但 UI 没日志

检查:

复制代码
GRPCLogClientAppender
Root Logger
Agent Activation
11800
OAP 日志接收器
时间与服务筛选

6. 告警不触发

检查:

复制代码
告警 Metric 名称是否正确
表达式是否正确
period 是否满足
请求是否跨多个分钟桶
规则是否限定了 include-names
alarm-settings.yml 是否被 OAP 加载
Webhook URL 是否正确
alarm-service 是否监听 8084

7. Profiling 没数据

检查:

复制代码
Agent Profiling 是否启用
OAP receiver-profile 是否启用
任务 Endpoint 是否与真实 Operation Name 一致
任务是否在有效时间内
请求是否超过 Min Duration Threshold
Max Sampling Count 是否已达到
请求是否在任务创建后重新发起

二十、整套源码中的核心类

领域 核心类
Agent 启动 SkyWalkingAgent
插件发现 PluginFinder
字节码增强 Transformer
上下文入口 ContextManager
本地追踪上下文 TracingContext
一段本地追踪 TraceSegment
HTTP 入口 Tomcat Interceptor
跨服务出口 Feign/HTTP Client Interceptor
JDBC 追踪 JDBC Statement Wrapper
HikariCP Pooling Interceptor
Toolkit Trace Annotation Interceptor
Trace 上报 TraceSegmentServiceClient
OAP Trace 接收 TraceSegmentReportServiceHandler
OAP 解析 TraceAnalyzer
服务关系分析 RPCAnalysisListener
日志上传 LogReportServiceClient
告警核心 AlarmCoreRunningRule
Profiling 执行 ProfileTaskExecutionService
线程采样 ThreadProfiler
Profiling 分析 ProfileAnalyzer

二十一、一段完整的面试回答

面试官问:

请整体介绍 SkyWalking 的工作原理。

可以回答:

SkyWalking 主要由 Java Agent、OAP、存储和 UI 组成。Java 应用通过 -javaagent 在启动阶段执行 Agent 的 premain,Agent 加载插件并使用 Byte Buddy 与 Instrumentation 对 Tomcat、Feign、JDBC、连接池等框架类进行字节码增强。请求进入 Tomcat 时创建 EntrySpan,业务内部通过 Toolkit 可以创建 LocalSpan,对外调用 Feign 或数据库时创建 ExitSpan。ContextManager 使用 ThreadLocal 为每个请求线程维护独立的 TracingContext,并通过活动 Span 栈确定父子关系。跨服务时,调用方将 Trace 上下文注入 HTTP Header,下游从 Header 中恢复上下文,从而生成属于同一 Trace 的不同 Segment。
当所有 Span 结束后,Agent 将 TraceSegment 放入内存缓冲区,由后台线程转换成 Protobuf 对象并通过 gRPC 上报 OAP,避免网络发送阻塞业务线程。OAP 对 Segment 中的 Entry、Exit 和 Local Span 进行分析,生成 Service、Endpoint、ServiceRelation、DatabaseAccess 等 Source,再通过 OAL 聚合成响应时间、成功率、CPM、百分位等 Metrics,用于仪表盘、拓扑和告警。日志可通过 Toolkit 获取 Trace ID,并以结构化 LogData 携带 Trace ID、Segment ID 和 Span ID 上传,实现日志与链路关联。告警模块对分钟级 Metrics 维护滑动窗口,规则命中后通过 WebHook 通知外部系统。对于 Span 内部难以定位的慢方法,还可以通过 Trace Profiling 周期采样目标请求线程栈,由 OAP 合并计算 Duration、Self Duration 和 Dump Count。


二十二、你目前对 SkyWalking 的掌握层次

现在已经不只是会:

复制代码
启动 OAP
打开 UI
查看 Trace

而是已经贯通了:

复制代码
-javaagent 启动
插件匹配
字节码增强
Entry / Local / Exit Span
ThreadLocal 上下文
Span 栈
跨服务传播
数据库分层诊断
Segment 异步上报
OAP Source 与 Metrics
服务拓扑
日志关联
告警时间窗口
Trace Profiling

按后端面试标准,可以归纳为三个层次:

层次 当前状态
会部署和使用 已掌握
会根据 Trace 定位问题 已掌握
能解释 Agent 与 OAP 核心原理 已形成完整主线
能独立修改 Agent/OAP 源码 尚需专项源码工程训练

二十三、最终记忆图

复制代码
-javaagent
    ↓
premain
    ↓
配置加载 + 插件扫描
    ↓
Byte Buddy + Instrumentation
    ↓
框架类被增强
    ↓
HTTP 请求进入
    ↓
EntrySpan
    ↓
LocalSpan / ExitSpan
    ↓
ContextManager + ThreadLocal + Span Stack
    ↓
跨服务 ContextCarrier
    ↓
TraceSegment
    ↓
Agent 内存队列
    ↓
gRPC 11800
    ↓
OAP TraceAnalyzer
    ↓
Source
    ↓
OAL Metrics
    ├─ Dashboard
    ├─ Topology
    ├─ Alarm
    └─ Storage

同时:

业务日志
→ LogData
→ TraceContext
→ OAP 日志关联

Profiling Task
→ Thread.getStackTrace()
→ Snapshot
→ OAP 合并分析

下一阶段建议不再继续平铺源码,而是进入 SkyWalking 面试拷打与故障实战复盘:从 Agent 不上报、链路断裂、连接池耗尽、慢 SQL、日志丢失、告警不触发等场景,要求你先判断问题层级,再给出排查顺序。

相关推荐
大模型码小白1 小时前
在 Windows 下 Codex 安装、配置与使用详细指南
java·人工智能·windows
Gauss松鼠会2 小时前
【GaussDB】GaussDB锁阻塞源头查询
java·开发语言·前端·数据库·算法·gaussdb·经验总结
mingo_敏2 小时前
DeepAgents : 后端(Backends)
java·开发语言
霸道流氓气质2 小时前
SpringBoot中事务内同步处理 + 事务后异步调用外部系统的通用模式示例
java·spring boot·后端
霸道流氓气质2 小时前
Spring 事务传播机制与 REQUIRES_NEW
java·数据库·spring
BerryS3N2 小时前
Java 后端转型大模型:Demo 能跑不等于能上线
java·人工智能·python·java后端·spring ai·langchain4j·大模型转型
心机之蛙qee2 小时前
Docker Compose 多容器编排
java·docker·容器
秋田君3 小时前
QT_QColorDialog颜色对话框
java·数据库·qt
用户91366296220124 小时前
ViewModelScope 简介与使用
java