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

SpringCloud——SkyWalking全链路监控源码深度解析 目录SkyWalking 源码复盘 ⑫从一次请求到监控结果的完整源码地图一、SkyWalking 整体分层二、第一阶段JVM 启动 Agent三、第二阶段插件匹配与字节码增强四、第三阶段HTTP 请求创建 EntrySpan五、第四阶段ContextManager 管理当前 TraceSpan 栈六、第五阶段Trace 创建业务 LocalSpan七、第六阶段HikariCP 与 JDBC 拆分数据库耗时1. 获取连接2. 执行 SQL3. 归还连接你的两个实验SSH 隧道断开/order/slowSql?time2八、第七阶段Feign 创建 ExitSpan 并传播上下文九、第八阶段TraceSegment 异步上报缓冲区满了怎么办十、OAP 接收并解析 Segment十一、为什么一条 Trace 能产生仪表盘指标十二、拓扑图是如何形成的十三、原始 Trace 与 Metrics 是两条不同链路十四、日志关联链路1. %tid 输出 Trace ID2. GRPCLogClientAppender 上传日志控制台有 TID但 UI 没日志十五、告警链路为什么 WebHook 是 JSON 数组十六、你的告警完整链路十七、Trace Profiling 链路三个核心指标Dump CountDurationSelf Duration判断方法十八、四条核心数据链链路一Trace链路二Metrics 与拓扑链路三日志链路四告警十九、故障排查的标准顺序1. UI 看不到服务2. 服务有指标但没有 Trace3. Trace 断链4. Trace 显示数据库慢5. 控制台有 TID但 UI 没日志6. 告警不触发7. Profiling 没数据二十、整套源码中的核心类二十一、一段完整的面试回答二十二、你目前对 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 │ └─────────────────────────────────────────┘端口必须记住端口用途11800Agent 向 OAP 上报数据12800UI 查询 OAP8080SkyWalking UI8084你的告警接收服务二、第一阶段JVM 启动 Agent你的启动参数类似-javaagent:H:\tools\...\skywalking-agent.jar -Dskywalking.agent.service_nameorder-service -Dskywalking.collector.backend_service127.0.0.1:11800JVM 启动顺序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 └─ ThreadLocalTracingContext ├─ 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 最后结束 TomcatSpan 栈为空后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 LocalSpan1. 获取连接HikariCP/Connection/getConnection回答应用拿到数据库连接花了多久慢时重点排查连接池耗尽 等待其他线程归还连接 数据库不可达 SSH 隧道断开 建立新连接过慢 连接泄漏2. 执行 SQLMysql/JDBC/PreparedStatement/execute回答从调用 JDBC 到数据库返回整体花了多久慢时重点排查SQL 缺少索引 锁等待或死锁 扫描行数过多 数据库 CPU、磁盘 IO 高 网络延迟 SSH 隧道拥塞 结果集过大3. 归还连接HikariCP/Connection/close一般不是关闭物理连接而是清理连接状态 → 归还连接池 → 供其他请求复用你的两个实验SSH 隧道断开HikariCP/getConnection ≈ 30 秒结论SQL 尚未真正执行 主要慢在获取或创建连接/order/slowSql?time2getConnection 很短 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 ├─ EntrySpanPOST:/order/create └─ ExitSpan/account/deduct │ │ Trace Header ▼ account-service └─ EntrySpanPUT:/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 接收并解析 SegmentOAP 接收路径TraceSegmentReportServiceHandler → SegmentParserService → TraceAnalyzerTraceAnalyzer遍历EntrySpan ExitSpan LocalSpan Segment并通知不同监听器RPCAnalysisListener SegmentAnalysisListener VirtualServiceAnalysisListener ……监听器再生成统一的 SourceService ServiceInstance Endpoint ServiceRelation EndpointRelation DatabaseAccess Segment随后SourceReceiver → Dispatcher → OAL → Metrics → Storage十一、为什么一条 Trace 能产生仪表盘指标入口 SpanGET:/order/slowSql Duration ≈ 2100 ms可以生成Serviceorder-service ServiceInstanceorder 实例 EndpointGET:/order/slowSqlOAL 再计算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 ExitSpanMySQL 没有安装 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 IDLogback Pattern → %tid → Agent 增强 PatternConverter → ContextManager.getGlobalTraceId() → 输出到控制台例如[TID:abc123] 查询订单orderId1它主要方便人肉排查。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 统一保存到ListAlarmMessage然后gson.toJson(messages)所以请求体是[ { scope: ENDPOINT, name: GET:/order/slowSql }, { scope: DATABASE, name: 127.0.0.1:13306 } ]你的 Controller 应使用RequestBody ListAlarmMessage messages十六、你的告警完整链路PowerShell 循环请求 → GET /order/slowSql?time2 → JDBC execute 约 2 秒 → Agent 上报 Segment → OAP 生成分钟级 Metrics → RunningRule 窗口满足条件 → 创建 ListAlarmMessage → POST alarm-service:8084 → AlarmController → 邮件服务 → QQ SMTP → 邮件到达WebHook 或邮件发送失败不会直接影响 order-service 请求因为告警发生在 OAP 的独立链路中。但 WebHook 本身仍应快速返回邮件发送最好异步化。十七、Trace Profiling 链路任务创建UI → OAP 保存 ProfileTaskAgent 定时查询ProfileTaskChannelService → getProfileTaskCommands()收到任务后校验任务 → 等待 Start Time → 启动 ProfileTaskExecutionContext新的请求到达创建 TracingContext → firstSpanOPName 与任务 Endpoint 匹配 → 创建 ThreadProfiler → 状态 PENDING请求运行超过Min Duration Threshold后PENDING → PROFILINGProfiler 周期执行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 是否监听 80847. Profiling 没数据检查Agent Profiling 是否启用 OAP receiver-profile 是否启用 任务 Endpoint 是否与真实 Operation Name 一致 任务是否在有效时间内 请求是否超过 Min Duration Threshold Max Sampling Count 是否已达到 请求是否在任务创建后重新发起二十、整套源码中的核心类领域核心类Agent 启动SkyWalkingAgent插件发现PluginFinder字节码增强Transformer上下文入口ContextManager本地追踪上下文TracingContext一段本地追踪TraceSegmentHTTP 入口Tomcat Interceptor跨服务出口Feign/HTTP Client InterceptorJDBC 追踪JDBC Statement WrapperHikariCPPooling InterceptorToolkitTrace Annotation InterceptorTrace 上报TraceSegmentServiceClientOAP Trace 接收TraceSegmentReportServiceHandlerOAP 解析TraceAnalyzer服务关系分析RPCAnalysisListener日志上传LogReportServiceClient告警核心AlarmCore、RunningRuleProfiling 执行ProfileTaskExecutionService线程采样ThreadProfilerProfiling 分析ProfileAnalyzer二十一、一段完整的面试回答面试官问请整体介绍 SkyWalking 的工作原理。可以回答SkyWalking 主要由 Java Agent、OAP、存储和 UI 组成。Java 应用通过-javaagent在启动阶段执行 Agent 的premainAgent 加载插件并使用 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、日志丢失、告警不触发等场景要求你先判断问题层级再给出排查顺序。