上一篇【第32篇】上下文传播协议——分布式链路是如何穿针引线的
下一篇【第34篇】TraceId的生成算法——一个ID背后的精巧工程


1. 引言:协议也需要进化

如果说v2协议是一台"功能完备但有些粗糙的燃油车",那v3协议就是"油电混合的新能源车"——核心传动系统(Trace模型)没变,但燃油效率(编码效率)更高,还多了电池驱动(Baggage透传)和智能辅助驾驶(跨语言支持)。

SkyWalking从7.x版本开始全面采用v3协议。这篇文章带你理解v3到底改了些什么,这些改动对你意味着什么。

2. v3相比v2的改进全景

2.1 改进清单

+------------------------------------------------------------------+
|      v2 → v3 协议改进总览                                          |
+------------------------------------------------------------------+
|                                                                    |
|  ┌─────────────────────────┬────────────────────────────────────┐ |
|  │  改进方向                │  具体变化                          │  │
|  ├─────────────────────────┼────────────────────────────────────┤  │
|  │  ① 编码简化             │  TraceId从32字符→三段式             │  │
|  │                         │  SegmentId从32字符→三段式           │  │
|  │                         │  取消冗余字段                       │  │
|  ├─────────────────────────┼────────────────────────────────────┤  │
|  │  ② 性能优化             │  protobuf字段精简                   │  │
|  │                         │  减少不必要的序列化字段             │  │
|  ├─────────────────────────┼────────────────────────────────────┤  │
|  │  ③ Baggage透传          │  新增sw8-correlation Header         │  │
|  │                         │  支持业务自定义键值对透传           │  │
|  ├─────────────────────────┼────────────────────────────────────┤  │
|  │  ④ 跨语言兼容           │  标准化protobuf定义                 │  │
|  │                         │  Java/Go/Python/Node.js共用一个.proto│  │
|  ├─────────────────────────┼────────────────────────────────────┤  │
|  │  ⑤ 可观测性增强         │  支持实例属性上报                   │  │
|  │                         │  支持更细粒度的端点匹配             │  │
|  └─────────────────────────┴────────────────────────────────────┘ │
+------------------------------------------------------------------+

2.2 向后兼容策略

v3的设计坚持了**"不破不立"中"不破"的部分**:

// OAP Server同时支持v2和v3协议
// config/application.yml
core:
  grpc:
    # 同时监听v2和v3端口
    host: 0.0.0.0
    port: 11800  # 主端口(v3,兼容v2)
    
// v2的Agent可以连接v3的OAP(OAP兼容解析)
// v3的Agent也可以连接支持v2的OAP(Agent兼容发送)

3. 透传业务信息(Baggage)的实现

3.1 什么是Baggage?

“Baggage”(行李)是一个形象的名字——它允许业务代码把一些自定义的键值对挂在Trace上下文中,跟随请求一起传播到下游服务。

+------------------------------------------------------------------+
|      Baggage 透传示意图                                            |
+------------------------------------------------------------------+
|                                                                    |
|  Service-A                               Service-B                |
|  ┌──────────────────────┐              ┌──────────────────────┐   │
|  │                      │              │                      │   |
|  │ TraceContext         │              │ TraceContext         │   |
|  │  traceId: abc123     │              │  traceId: abc123     │   |
|  │                      │              │                      │   |
|  │ Baggage:             │    RPC       │ Baggage:             │   |
|  │  userId: 10086    ───┼─────────────→│  userId: 10086       │   |
|  │  tenantId: acme   ───┤  带着行李走  │  tenantId: acme      │   |
|  │  orderType: VIP   ───┼─────────────→│  orderType: VIP      │   |
|  │                      │              │                      │   |
|  └──────────────────────┘              └──────────────────────┘   |
|                                                                    |
|  HTTP Header:                                                     |
|  sw8-correlation:                                                  |
|    base64(userId),base64(10086),                                   |
|    base64(tenantId),base64(acme),                                  |
|    base64(orderType),base64(VIP)                                   |
+------------------------------------------------------------------+

3.2 Baggage的使用方式

// ===== 方式1: Java Agent Toolkit API =====
import org.apache.skywalking.apm.toolkit.trace.TraceContext;

@RestController
public class OrderController {
    
    @GetMapping("/api/order/{orderId}")
    public Order getOrder(@PathVariable String orderId, 
                          HttpServletRequest request) {
        
        // ① 写入Baggage
        TraceContext.putCorrelation("userId", getCurrentUserId());
        TraceContext.putCorrelation("tenantId", getTenantId());
        TraceContext.putCorrelation("orderId", orderId);
        
        // ② 读取Baggage(在当前服务或下游服务都可以读)
        String userId = TraceContext.getCorrelation("userId")
            .orElse("unknown");
        
        // ③ 这些Baggage会自动通过sw8-correlation Header传播到下游
        return orderService.getOrder(orderId);
    }
}

// ===== 方式2: 通过SkyWalking注解 =====
@Service
public class PaymentService {
    
    @Trace(operationName = "processPayment")
    public PaymentResult process(PaymentRequest request) {
        // 在下游服务中读取上游透传的Baggage
        String tenantId = TraceContext.getCorrelation("tenantId")
            .orElse("default");
        
        // 根据租户ID选择不同的支付策略
        if ("vip-tenant".equals(tenantId)) {
            return vipPaymentProcessor.process(request);
        } else {
            return normalPaymentProcessor.process(request);
        }
    }
}

3.3 Baggage的编码协议

// sw8-correlation的编码格式
// Header Key: sw8-correlation
// Header Value: 连续的base64(key),base64(value)对

public class CorrelationContext {
    
    // 编码:将键值对编码为sw8-correlation Header值
    public static String serialize(Map<String, String> correlations) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : correlations.entrySet()) {
            if (sb.length() > 0) {
                sb.append(",");
            }
            sb.append(Base64.encode(entry.getKey()));
            sb.append(",");
            sb.append(Base64.encode(entry.getValue()));
        }
        return sb.toString();
    }
    
    // 解码:从sw8-correlation Header值还原键值对
    public static Map<String, String> deserialize(String headerValue) {
        Map<String, String> result = new LinkedHashMap<>();
        String[] parts = headerValue.split(",");
        for (int i = 0; i < parts.length - 1; i += 2) {
            String key = Base64.decode(parts[i]);
            String value = Base64.decode(parts[i + 1]);
            result.put(key, value);
        }
        return result;
    }
}

// 编码示例:
// 输入: {userId:"10086", tenantId:"acme-corp"}
// 输出: dXNlcklk,MTAwODY=,dGVuYW50SWQ=,YWNtZS1jb3Jw

3.4 Baggage vs 业务Header

你可能会问:“我在HTTP Header里自己加个X-UserId不行吗?为什么要用Baggage?”

特性 自定义HTTP Header SkyWalking Baggage
自动传播 需要手动处理,每个框架都要写 Agent自动在所有框架中传播
跨协议 HTTP能传,但Dubbo/gRPC要额外处理 自动适配所有协议
上下文管理 需要手动在代码中管理 自动绑定到Trace上下文
可见性 只有自己能读 可以在Trace中看到(UI展示)
线程传播 跨线程需要手动传递 Agent自动处理跨线程
存储 无存储 可配置存储在OAP

3.5 Baggage的大小限制

# Baggage的总大小限制(防止Header过大)
# agent.config
correlation.element_max_number=3     # 最多3个Baggage键值对
correlation.value_max_length=128     # 每个值最多128字符

# 完整Header值示例(编码前):
# userId,10086,tenantId,acme-corp,orderId,ORD-2026-001
# 编码后约100字节左右

4. 跨语言协议兼容性

4.1 多语言Agent的协议一致性

v3协议的最大亮点之一是跨语言兼容

+------------------------------------------------------------------+
|      v3协议跨语言支持矩阵                                          |
+------------------------------------------------------------------+
|                                                                    |
|  ┌──────────────────────────────────────────────────────────────┐ │
|  │              统一 .proto 定义                                 │ │
|  │  ┌──────────────────────────────────────────────────────┐    │ │
|  │  │  skywalking-data-collect-protocol v3                 │    │ │
|  │  │  language-agent-v3/*.proto                          │    │ │
|  │  └──────────────────────────────────────────────────────┘    │ │
|  │                          │                                    │ │
|  │     ┌────────────────────┼────────────────────┐               │ │
|  │     │                    │                    │               │ │
|  │     ▼                    ▼                    ▼               │ │
|  │  ┌────────┐         ┌────────┐         ┌────────┐             │ │
|  │  │  Java  │         │   Go   │         │ Python │             │ │
|  │  │ Agent  │         │  Agent │         │ Agent  │             │ │
|  │  └───┬────┘         └───┬────┘         └───┬────┘             │ │
|  │      │                  │                  │                  │ │
|  │      │         protoc编译器生成各语言Stub   │                  │ │
|  │      │                  │                  │                  │ │
|  │      ▼                  ▼                  ▼                  │ │
|  │  ┌──────────────────────────────────────────────────────┐    │ │
|  │  │  Java: SegmentObject.java   (生成的Java类)            │    │ │
|  │  │  Go:   segment_object.pb.go (生成的Go结构体)          │    │ │
|  │  │  Py:   segment_object_pb2.py (生成的Python类)         │    │ │
|  │  └──────────────────────────────────────────────────────┘    │ │
|  └──────────────────────────────────────────────────────────────┘ │
|                                                                    |
|  关键: 无论哪种语言生成,网络上的二进制数据完全一致!              │
+------------------------------------------------------------------+

4.2 跨语言场景的实用示例

+------------------------------------------------------------------+
|       实际跨语言场景                                                |
+------------------------------------------------------------------+
|                                                                    |
|  HTTP Request                                                      |
|       │                                                            |
|       ▼                                                            |
|  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐       |
|  │ Gateway      │────→│ Order Service│────→│ AI Service   │       │
|  │ (Java Agent) │     │ (Go Agent)   │     │ (Python Agent│       │
|  └──────────────┘     └──────────────┘     │  推理服务)    │       │
|                                             └──────────────┘       │
|                                                                    |
|  全程Trace串通,因为:                                              │
|  1. HTTP Header中的SW8格式跨语言统一                                │
|  2. protobuf序列化的二进制格式跨语言一致                            │
|  3. gRPC通信协议跨语言标准                                          │
+------------------------------------------------------------------+

5. v3协议的字段设计

5.1 关键新增字段

// v3 SpanObject 新增字段
message SpanObject {
    // ... v2已有字段 ...
    
    // v3新增: Span类型枚举(替代v2的隐式类型判断)
    SpanType spanType = 19;
    
    // v3新增: 服务编码(用于跨语言一致的服务标识)
    string serviceCode = 20;
    
    // v3新增: 端点名称(替代v2中需要解析operationName的方式)
    string endpointName = 21;
    
    // v3新增: 层级(http/rpc/db/cache/mq)
    SpanLayer spanLayer = 22;
    
    // v3新增: 组件ID(标识具体的框架/库)
    int32 componentId = 23;
}

// v3 新增消息类型
message SegmentCollection {
    repeated SegmentObject segments = 1;
}

// v3 新增
message ServiceInstancePingPkg {
    int32 serviceInstanceId = 1;
    int64 time = 2;
    string serviceInstanceUUID = 3;
}

5.2 字段对比表

字段 v2 v3 说明
spanType 无(通过其他方式推断) SpanType枚举 更明确的类型标识
spanLayer SpanLayer枚举 标识调用层级
componentId 有(部分) 完整的官方组件列表 跨语言统一
serviceCode string 跨语言服务统一标识
endpointName string 端点唯一名称
instance属性 Properties map 自定义实例标签

6. 从v2迁移到v3的注意事项

6.1 迁移checklist

+------------------------------------------------------------------+
|       v2 → v3 迁移检查清单                                        |
+------------------------------------------------------------------+
|                                                                    |
|  ✅ 1. OAP Server升级                                             |
|     - 备份ES数据                                                   |
|     - 升级OAP版本(7.x+自动启用v3协议)                            |
|     - 验证OAP正常启动并监听11800端口                               |
|                                                                    |
|  ✅ 2. Agent升级                                                   |
|     - 升级Java Agent版本(8.x+)                                   |
|     - 升级其他语言Agent(Go/Python/Node.js等)                     |
|     - 验证Agent连接OAP正常                                         |
|                                                                    |
|  ✅ 3. 验证追踪连续性                                              |
|     - 发送测试请求,确认Trace在UI中完整显示                         |
|     - 检查跨服务调用是否正常串联                                   |
|                                                                    |
|  ✅ 4. 检查自定义扩展                                              |
|     - 如果有自定义OAL脚本,确认兼容                                |
|     - 如果有自定义Webhook,确认告警正常                            |
|     - 如果有自定义Dashboard,确认展示正常                          |
|                                                                    |
|  ⚠️ 5. 潜在问题                                                   |
|     - v2和v3的TraceId格式不同,旧数据无法与新数据关联               |
|     - v3新增了Baggage,旧版本不支持透传                             |
+------------------------------------------------------------------+

6.2 滚动升级策略

# 推荐的迁移顺序(金丝雀发布)
# 
# 阶段1: 升级OAP(保持兼容v2)
#   - 先升级1个OAP节点 → 观察 → 升级所有OAP节点
#
# 阶段2: 灰度升级部分Agent
#   - 选择一个低风险服务 → 升级Agent → 观察Trace
#   - 逐步扩大范围
#
# 阶段3: 全量升级
#   - 升级所有服务的Agent
#   - 验证全链路追踪正常
#
# 阶段4: 启用v3新特性
#   - 配置Baggage透传
#   - 配置跨语言Agent

6.3 常见迁移问题

# 问题1: 升级后Agent连接不上OAP
# 原因: 端口配置错误或防火墙
# 解决:
# 检查OAP日志
kubectl logs skywalking-oap-0 -n skywalking | grep 11800

# 检查Agent配置
cat agent/config/agent.config | grep backend_service

# 问题2: Trace断链
# 原因: v2和v3的Header格式不完全兼容
# 解决: 确保所有Agent都升级到v3(或至少使用兼容模式)

# 问题3: Baggage不传播
# 原因: 需要显式启用(部分版本)
# 解决:
# agent.config
correlation.auto_tag_keys=userId,tenantId,orderId

7. 总结

v3协议是SkyWalking走向生产级、跨语言、全功能的重要里程碑:

  1. 编码简化:三段式ID从协议层面就包含了更多信息
  2. Baggage透传:业务信息随Trace传播,日志关联、租户路由、灰度发布等场景受益
  3. 跨语言兼容:统一的protobuf定义,Java/Go/Python/Node.js共享同一套协议
  4. 向后兼容:v3 OAP兼容接收v2 Agent数据,平滑迁移
  5. 字段增强:更明确的SpanType/SpanLayer/ComponentId,跨语言Agent无缝对接

v3不是推倒重来,而是在v2坚实的基座上,添砖加瓦,向着更大的生态迈进。

下一篇文章——本系列最后一篇——我们将深入一个看似简单但极其精妙的算法:SkyWalking TraceId的生成算法


上一篇【第32篇】上下文传播协议——分布式链路是如何穿针引线的
下一篇【第34篇】TraceId的生成算法——一个ID背后的精巧工程


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐