先抛出核心结论:
依靠透传链路上下文(SW8 Header) + 全局唯一 TraceID + Span 父子关系 + OAP 服务端按 TraceID 归并重组整个流程分为客户端传递、各个服务埋点采集、后端聚合重组三大部分。
一、基础概念前置
- TraceId:一次用户请求全局唯一标识,整条链路所有节点共用同一个 TraceId。
- SpanId:当前调用段编号;Parent-SpanId:上游调用方的 SpanId。依靠这两个字段构建树形调用树。
- SW8(SkyWalking 自定义传播协议头)HTTP/Dubbo/RPC/MQ 传输载体,链路上下文载体,是跨服务串联的关键。
很多人混淆: Zipkin/Sleuth 使用
X-B3-TraceId;SkyWalking默认不兼容 B3,自有sw8/sw8-correlation协议头。
二、完整时序:跨服务调用链路生成 & 传递
场景示例:网关Gateway → 订单服务Order → 库存服务Stock
阶段 1:请求入口(Gateway,链路起点)
- Agent 拦截网关接收的 HTTP 请求,生成全新 TraceId
- 创建第一个 Span(入口 Span)
- 组装链路上下文:TraceId、当前 SpanId、ParentSpanId(起点父 ID 为空)
- 序列化成二进制数据,放入SW8 请求 Header
阶段 2:Gateway 调用下游【订单服务】
Gateway 使用 OpenFeign/Dubbo 发起远程调用 Agent 拦截 RPC 客户端请求: ✅ 从当前线程上下文中取出链路信息 ✅ 把sw8header 塞进 RPC 请求 / HTTP 请求 请求发往 Order 服务
阶段 3:订单服务接收请求
Order 服务的 Agent 拦截服务入口(Controller/Dubbo 服务方法):
- 从请求中提取 SW8 Header,反序列化拿到 TraceId、上游 SpanId
- 新建当前服务的 Span,设置:
- TraceId = 上游传递过来的 TraceId(整条链路不变!重点)
- ParentSpanId = 上游的 SpanId
- 执行业务;当 Order 继续调用 Stock 库存服务 Agent 再次把最新上下文封装进 SW8,传给 Stock
阶段 4:库存服务执行完毕,逐级返回
每个服务执行结束,Agent 组装当前 Span 信息(耗时、状态、异常、接口名、实例信息)异步通过 gRPC 发送 Span 数据到 SkyWalking OAP Server
⚠️关键点: 各个微服务互相独立上报各自的 Span! 订单服务不知道库存服务的 Span,网关也不知道下游情况。所有 Span 是分散上报,后端 OAP 负责聚合,业务服务之间不互相传递监控数据。
三、OAP 服务端:如何把分散的 Span 聚合成完整链路?
各个节点的 Span 源源不断异步上报到 OAP: 每条 Span 字段至少包含:traceId、spanId、parentSpanId、serviceName、instance、endpoint、耗时、异常
聚合逻辑步骤:
- OAP 收到单条 Span,以 traceId 作为分组 Key,存入时序存储(ES/BanyanDB)
- 前端 UI 查询链路时,传入 TraceId;
- 存储层检索出拥有同一个 TraceId 的全部 Span(来自网关、订单、库存所有节点)
- OAP 内存中根据
spanId ↔ parentSpanId递归构建树形调用关系 - 按照调用起始时间排序,生成我们看到的瀑布图完整调用链
形象理解
各个微服务像不同工厂,各自写下「自己这段工序单据(Span)」,单据上都印着同一个工单编号(TraceId)。 所有单据统一邮寄到仓库(OAP 存储); 你查工单编号时,仓库把所有单据全部找出,依靠单据上的上下游编号拼接完整流程。
四、SW8 Header 内部结构(透传核心)
sw8 是经过 Base64 编码的二进制字节数组,内部承载信息:
TraceId SegmentId (一个服务实例内一段连续调用片段ID) SpanId ParentSegmentId ParentSpanId 采样标记Segment:SkyWalking 特有概念 一个服务实例上产生的一组连续 Span 叫做一个 Segment。 一条 Trace 由多个不同服务的 Segment组成。
Trace (全局) → 多个 Segment (不同微服务) → 多个 Span (方法调用)
这是和 Zipkin 比较大的区别,引入 Segment 优化大数据下存储结构。
五、高频踩坑:链路断裂(无法聚合)根本原因
链路拼不起来 =下游拿不到上游传递的 SW8 头,会生成新 TraceId,链路直接断开。 常见场景:
- 异步线程丢失上下文
✅解决方案:使用// 原生线程池,Agent无法自动传递链路上下文!新线程生成新Trace executor.execute(() -> orderTask());SWExecutors包装线程池,传递上下文 - MQ 消费者未正确传递上下文 使用 RabbitMQ/RocketMQ 跨服务通信,需要把 sw8 放入消息 headers SkyWalking 官方 MQ 插件已经封装实现,不要自己手动新建消息
- 跨网关、跨中间件时,过滤器丢弃未知 Header Nginx、Gateway 全局过滤器删除
sw8开头请求头 - 使用了 Agent 不支持的 HTTP 客户端 小众 http 工具类,缺少对应的 Agent 插件,无法自动注入 sw8
六、补充对比
- SkyWalking自有 sw8 协议,Segment 设计;JavaAgent 无侵入自动注入 / 解析 header; 链路聚合:OAP 服务端按 traceId 检索所有 segment 重组
- Pinpoint同样 agent 字节码增强,自定义协议;思路大体一致
- Zipkin / Spring Cloud Sleuth使用 B3 协议(traceId,spanId,parentId),Header:X-B3-TraceId; 存储直接保存完整链路结构,轻量,缺少指标计算能力
七、极简总结
SkyWalking 实现跨服务链路聚合分为三步:
- 上下文透传Java Agent 字节码增强拦截所有 RPC/HTTP 调用,在请求头携带自定义
sw8上下文,把全局唯一 TraceId、父 Span 信息传递给下游服务;整条请求全程 TraceId 保持不变。 - 各节点独立采集上报每个微服务本地生成各自调用 Span,异步独立上报到 OAP 服务端,服务之间不传输监控数据。
- 服务端归并重组OAP 以 TraceId 为维度检索全部分散 Span,依靠 spanId 与 parentSpanId 构建调用树,最终聚合形成完整分布式调用链路展示在 UI。