协议层与 Agent 采集

这一篇解决什么
前面的文档讲了 OAP receiver 怎么接收数据,但"线上跑的到底是什么数据结构"没展开。这一篇把协议本身挖到底:apm-protocol 全部 26 个 proto 文件、SegmentObject/SpanObject 逐字段、Command 反向下发机制、OAP 端采样策略(基于 traceId.hashCode() % 10000 的确定性桶)、Compat 兼容协议为什么能让旧 Agent 不断链。读完这篇,Agent ↔ OAP 的"语法"就通了。


apm-protocol 模块结构

apm-protocol 模块只有一个子模块 apm-network,把所有 proto 定义集中托管,通过 protoc 生成各语言桩代码。proto 文件按"数据域"分目录,每个目录对应一类采集能力。

proto 通用约定(新手必读)

所有 proto 共享下面几条约定,看懂这几条就能读懂任何一份:

  • package:绝大多数是 skywalking.v3;只有 asyncprofiler/AsyncProfiler.protopprof/Pprof.protoskywalking.v10(较新协议,独立版本号)。
  • java_package:生成代码落到 org.apache.skywalking.apm.network.<域>.v3,OAP 按 import 区分。
  • option java_multiple_files = true:每个 message 生成独立 Java 类,所以你在 OAP 源码里能直接 import ...SegmentObject
  • import “common/Common.proto” / “common/Command.proto”:被引用后,KeyStringValuePairCPUCommands 等可直接用。这也解释了为什么"几乎所有 service 的返回值都是 Commands"——它们都 import 了 Command.proto
  • service 定义:proto 里的 service X { rpc ... } 就是 gRPC 服务契约,Agent 是 client,OAP 是 server。
  • stream 关键字:rpc collect (stream SegmentObject) 表示客户端流式上传——Agent 在一条 gRPC 连接里连续发多条,returns (Commands) 是普通单条响应;双向流(stream ... returns (stream ...))只在 async-profiler/pprof 出现,用来让 OAP 中途控制 Agent 停止。

什么是 protobuf 和 gRPC streaming

  • protobuf(Protocol Buffers):Google 的二进制序列化格式。你写一份 .proto,描述数据的字段和类型,protoc 工具帮你生成各语言类。相比 JSON,它更小、更快、有强类型检查,代价是人不方便直接读。
  • gRPC streaming:普通 RPC 是"一问一答",gRPC 的 stream 关键字让一次调用可以连续发多条消息。stream 在参数侧叫客户端流(Agent 一直发,OAP 收),在返回值侧叫服务端流,两边都加 stream 是双向流。SkyWalking 的 trace 上报就是客户端流——一个请求的几十个 span 不一个个发,而是打包成一条流连续推上去。

proto 文件全量清单(26 个)

目录 文件 作用域
common/ Common.proto 公共基础类型(KeyStringValuePair、CPU、DetectPoint、Instant)
common/ Command.proto OAP 反向下发指令的 Command/Commands 结构
language-agent/ Tracing.proto trace 段上报(SegmentObject/SpanObject)
language-agent/ JVMMetric.proto JVM 指标上报
language-agent/ Meter.proto 自定义 Meter 上报
language-agent/ CLRMetric.proto .NET CLR 指标上报
language-agent/ ConfigurationDiscoveryService.proto 配置动态发现(CDS)
language-agent/ TracingCompat.proto / JVMMetricCompat.proto / MeterCompat.proto / CLRMetricCompat.proto 旧版包名兼容服务定义
logging/ Logging.proto 日志上报
event/ Event.proto 事件上报
profile/ Profile.proto 线程级 Profile 任务下发与快照上报
profile/ ProfileCompat.proto Profile 兼容服务
management/ Management.proto 实例注册/保活/属性上报
management/ ManagementCompat.proto 管理兼容服务
browser/ BrowserPerf.proto 浏览器性能/错误日志/Web Vitals 上报
browser/ BrowserPerfCompat.proto 浏览器兼容服务
service-mesh-probe/ service-mesh.proto service mesh(Envoy sidecar)指标上报
ebpf/ accesslog.proto eBPF(Rover)访问日志/网络内核指标
ebpf/profiling/ Process.proto eBPF 进程发现与保活
ebpf/profiling/ Profile.proto eBPF on/off CPU profiling 数据
ebpf/profiling/ Continuous.proto eBPF 连续 profiling 策略
asyncprofiler/ AsyncProfiler.proto async-profiler(JFR)任务(package skywalking.v10)
pprof/ Pprof.proto Go pprof 任务(package skywalking.v10)

各 proto 定义的核心 service 与 message 速览

proto 文件 service 核心 message
language-agent/Tracing.proto:33 TraceSegmentReportService(collect 流式 / collectInSync 单条) SegmentObjectSpanObjectSegmentReferenceLogSpanAttachedEventSegmentCollection
language-agent/Tracing.proto:235 SpanAttachedEventReportService(v3.1,eBPF 附加事件) SpanAttachedEvent
language-agent/JVMMetric.proto:32 JVMMetricReportService JVMMetricCollectionJVMMetricMemoryMemoryPoolGCThreadClass
language-agent/Meter.proto:29 MeterReportService(collect / collectBatch) MeterDataMeterSingleValueMeterHistogramMeterBucketValueLabelMeterDataCollection
language-agent/CLRMetric.proto:31 CLRMetricReportService CLRMetricCollectionCLRMetricClrGCClrThread
language-agent/ConfigurationDiscoveryService.proto:31 ConfigurationDiscoveryService ConfigurationSyncRequest
logging/Logging.proto:32 LogReportService LogDataLogDataBodyTextLog/JSONLog/YAMLLogTraceContextLogTags
event/Event.proto:30 EventService EventSourceType
profile/Profile.proto:30 ProfileTask ProfileTaskCommandQueryThreadSnapshotThreadStackProfileTaskFinishReportGoProfileData
management/Management.proto:32 ManagementService InstancePropertiesInstancePingPkg
browser/BrowserPerf.proto:31 BrowserPerfService BrowserPerfDataBrowserErrorLogBrowserWebVitalsPerfDataBrowserResourcePerfDataBrowserWebInteractionsPerfData
service-mesh-probe/service-mesh.proto:31 ServiceMeshMetricService ServiceMeshMetricsHTTPServiceMeshMetricTCPServiceMeshMetricMeshProbeDownstream
ebpf/accesslog.proto:29 EBPFAccessLogService EBPFAccessLogMessageAccessLogConnectionAccessLogKernelLogAccessLogHTTPProtocol
ebpf/profiling/Process.proto:31 EBPFProcessService EBPFProcessReportListEBPFProcessPropertiesEBPFProcessEntityMetadata
ebpf/profiling/Profile.proto:30 EBPFProfilingService EBPFProfilingTaskQueryEBPFProfilingDataEBPFOnCPUProfilingEBPFOffCPUProfiling
ebpf/profiling/Continuous.proto:29 ContinuousProfilingService ContinuousProfilingPolicyQueryContinuousProfilingReportContinuousProfilingCause
asyncprofiler/AsyncProfiler.proto:29 AsyncProfilerTask AsyncProfilerDataAsyncProfilerMetaDataAsyncProfilerTaskCommandQueryAsyncProfilingStatus
pprof/Pprof.proto:29 PprofTask PprofDataPprofMetaDataPprofTaskCommandQueryPprofProfilingStatus

common 不定义 service
common/Common.proto 只定义公共 message;common/Command.proto 只定义 Command/Commands,都不定义 service。


SegmentObject / SpanObject 结构详解(trace 核心)

Segment vs Span
Zipkin 这类链路追踪系统,一条 trace 就是一堆平铺的 span。SkyWalking 不一样:它要求"同一个进程(典型如 Java 的同一个线程)里的所有 span 必须先打包成一个 Segment,再整体上报"。

这样设计有两个好处:一是减少上报次数——一个请求一个 Segment,而不是几十个 span 各报一次;二是 Segment 本身就是一个分析单位。一条完整的链路通常由多个 Segment 跨进程拼接而成,每个进程报自己的 Segment。

SegmentObject 字段(trace 段对象)

定义见 apm-protocol/apm-network/src/main/proto/language-agent/Tracing.proto:54

字段 类型 含义
traceId string 全局 trace 唯一 ID,跨所有 Segment 共享
traceSegmentId string 本 Segment 的唯一 ID,其他 Segment 通过它引用本段作为父段
spans repeated SpanObject 本段包含的所有 span
service string 服务逻辑名(一组同行为的工作负载),拓扑图上的节点
serviceInstance string 服务实例名(典型是一个 OS 进程,如一个 pod)
isSizeLimited bool 本 Segment 是否因 span 太多被截断(Agent 可能为了性能丢弃部分 span)

注释(Tracing.proto:48-53)明确:在 Java 里"一个 Segment = 一个请求上下文在同一个线程里的所有 span";在没有线程概念的语言(如 Go)里,则表示"一个请求上下文跨 goroutine 的所有 span"。

SpanObject 字段(执行单元)

定义见 apm-protocol/apm-network/src/main/proto/language-agent/Tracing.proto:113

字段 类型 含义
spanId int32 本段内 span 编号,从 0 开始,段内唯一
parentSpanId int32 本段内父 span 编号;-1 表示这是根 span(segment 的第一个 span)
startTime / endTime int64 毫秒时间戳(1970-01-01 UTC 起算)
refs repeated SegmentReference 跨线程/跨进程的父段引用;通常只有一个,但 MQ 批量消费场景可能有多个
operationName string span 的逻辑名(如 HTTP URI、gRPC 方法签名);强烈建议不含参数,参数放 tags
peer string 对端地址(ip/host:port);Exit span 必填,用于构建拓扑(STAM 方法)
spanType SpanType Entry/Exit/Local
spanLayer SpanLayer 技术栈层级(DB/RPC/HTTP/MQ/Cache 等)
componentId int32 组件 ID,对应 component-libraries.yml 里预定义的框架(如 Tomcat=1)
isError bool 是否异常;决定后端成功率统计
tags repeated KeyStringValuePair 字符串键值对,承载参数/额外信息
logs repeated Log span 执行期间发生的事件(带时间戳的键值对)
skipAnalysis bool 为 true 时让后端跳过分析(Agent 比后端更了解服务角色时用)

关键枚举

SpanType(Tracing.proto:183-190):

含义 典型场景
Entry = 0 服务端入口 RPC 的服务端、MQ 的消费端
Exit = 1 客户端出口 RPC 的客户端、MQ 的生产端
Local = 2 本地代码 本地普通代码执行

SpanLayer(Tracing.proto:207-224):标识 span 的技术栈层级,用于分层统计:

含义
Unknown = 0
Database = 1 数据库客户端
RPCFramework = 2 RPC 客户端/服务端
Http = 3 HTTP,是更具体的 RPCFramework
MQ = 4 MQ 生产/消费
Cache = 5 缓存客户端
FAAS = 6 函数即服务
GenAI = 7 生成式 AI 服务,较新

SpanType 与 SpanLayer 正交
一个 HTTP 服务端 span 是 spanType=Entry, spanLayer=Http;一个 MySQL 查询 span 是 spanType=Exit, spanLayer=Database。Entry/Exit 决定拓扑角色,SpanLayer 决定分层统计。

RefType(Tracing.proto:198-204):

含义
CrossProcess = 0 引用指向另一个 OS 进程的 Segment(跨进程 RPC/MQ)
CrossThread = 1 引用指向同一进程内跨线程的 Segment(仅在有线程概念的语言用)

SegmentReference 字段(跨段引用)

定义见 Tracing.proto:80。这是把多个 Segment 串成一条链路的"胶水"。

字段 类型 含义
refType RefType CrossProcess / CrossThread
traceId string 全局 traceId(与子段相同)
parentTraceSegmentId string 父 Segment 的 ID
parentSpanId int32 父段中具体的父 span 编号
parentService string 父段所属服务逻辑名
parentServiceInstance string 父段所属实例名
parentEndpoint string 父段的 endpoint 名(父段第一个 Entry span 的名)
networkAddressUsedAtPeer string 父端 Exit span 使用的网络地址(如 127.0.0.1:913),是 STAM 拓扑分析的关键

traceId / segmentId / parentSpanId 的关系(链路拼接原理)

这是新手最容易混乱的地方,用一个例子讲清楚:

服务A 收到 HTTP 请求 → 调用 服务B

图1

要点:

  • traceId 全链路唯一,所有 Segment 共享。
  • traceSegmentId 段内唯一,是"被引用"的句柄。
  • parentSpanId 有两层含义:段内(SpanObject.parentSpanId,指向本段内的父 span)和跨段(SegmentReference.parentSpanId,指向父段里的某个 span)。

tag 与 log 字段

  • tag(SpanObject.tags,Tracing.proto:163):repeated KeyStringValuePair,纯字符串键值对。某些特殊 tag 会被 OAP 用于高级特性(如 Java 插件开发指南里的 special span tags)。通俗说,tag 是 span 的"贴标签",比如 db.statement="SELECT * FROM users"http.method=GET
  • log(SpanObject.logs,Tracing.proto:166Log 定义于 Tracing.proto:174):repeated Log,每个 Log 是 {time: int64 毫秒时间戳, data: repeated KeyStringValuePair}。用于记录 span 执行期间的事件,典型用法是发生异常时把异常类名、消息、堆栈作为 key-value 存进去。

SpanAttachedEvent(v3.1,eBPF 附加事件)

定义见 Tracing.proto:246,由 SpanAttachedEventReportService(Tracing.proto:235)流式上报。

这是 Rover 的功能
当一个 RPC 正被进程内语言 Agent trace 时,SkyWalking Rover(eBPF Agent)通过 trace header(sw8)感知到这次 RPC,然后从 OS 内核层面(syscall 级)采集额外的网络诊断信息,"附加"到这个 span 上。

字段 类型 含义
startTime / endTime Instant 纳秒时间戳(注意:SkyWalking 多数时间戳是毫秒,这里因 syscall 极快而用纳秒)
event string 事件名(如 syscall 栈里的方法签名)
tags repeated KeyStringValuePair OS 级信息(如 net_device、L7 协议)
summary repeated KeyIntValuePair 统计汇总(name → int64)
traceContext SpanReference sw8 header 解码出的 trace 上下文(traceId/traceSegmentId/spanId)

Instant(common/Common.proto:58)是纳秒级时间点:seconds(epoch 秒)+ nanos(秒内纳秒,0-999999999)。

SpanReference.SpanReferenceType(Tracing.proto:284)支持 SKYWALKING = 0ZIPKIN = 1,即 Rover 既能附加到 SkyWalking trace,也能附加到 Zipkin trace。


Command 下发机制

为什么几乎所有 rpc 都返回 Commands
SkyWalking 的 gRPC 是双向的。Agent 上报数据的同时,OAP 借助每个 rpc 的返回值 Commands 反向给 Agent 下发指令——采样率调整、Profile 任务、配置更新、eBPF profiling 任务等。Agent 不需要额外的轮询通道。

复用 Agent 已经在建立的 gRPC 连接做反向控��。Agent 每次上报(trace/JVM/meter/log…),OAP 都可以顺带把"接下来你要做什么"塞进响应里,免去 Agent 单独长轮询配置/任务的开销。这也是为什么这些 proto 全都 import "common/Command.proto"

什么是 Command 下发
“Command 下发"就是 OAP 在 Agent 上报数据的回程里,夹带一条指令给 Agent 执��。比如 OAP 想 profile 某个慢接口,不用等 Agent 来问,而是趁 Agent 下一次上报 trace 时,在返回值里塞一句"去采集这个 endpoint 的线程快照”。Agent 收到后照做,下次上报时再把结果带回来。本质是把"控制流"搭在"数据流"的回程上,省一条独立通道。

Command / Commands 结构

定义见 apm-protocol/apm-network/src/main/proto/common/Command.proto

Command(Command.proto:99):

字段 类型 含义
command string 指令名,区分不同指令类型(如 ProfileTaskQueryConfigurationDiscoveryCommand)
args repeated KeyStringValuePair 指令参数,键值对;基本类型转字符串、列表用逗号分隔、复杂结构用 JSON 序列化

Commands(Command.proto:112):repeated Command commands,一次可下发多条指令。

通用信封设计
Command 是"无 schema 的通用信封"——command 名字 + args 键值对。具体每条指令有哪些 arg,由注释文档约定(见 Command.proto:32-98),不强制在 proto 里枚举。新增指令类型不用改 proto,只需新增一个指令名和约定 args。

OAP 侧的 Command 类型实现

OAP 在 apm-protocol/apm-network/src/main/java/org/apache/skywalking/oap/server/network/trace/component/command/ 下为每种指令写了 Java 封装类,统一继承 BaseCommand,实现 Serializable(OAP→Agent 方向序列化)和 Deserializable(Agent→OAP 方向反序列化,用于去重/确认)。

BaseCommand(BaseCommand.java:24):所有指令都带 command 名和 SerialNumber(序列号,Agent 用于指令去重,避免重复执行同一指令)。它内部维护一个 Command.Builder,构造时先把 SerialNumber 塞进去(BaseCommand.java:36)。

已实现的指令类型:

Java 类 指令名(NAME 常量) 说明
ProfileTaskCommand.java:28 ProfileTaskQuery 给 Agent 下发线程级 Profile 任务(taskId、endpoint、duration、dumpPeriod 等)
ConfigurationDiscoveryCommand.java:29 ConfigurationDiscoveryCommand CDS 配置发现,含 UUID(配置指纹)和 config 键值对
EBPFProfilingTaskCommand.java EBPFProfilingTaskQuery 给 Rover 下发 eBPF profiling 任务
ContinuousProfilingPolicyCommand.java ContinuousProfilingPolicyQuery 下发连续 profiling 策略(阈值监控)
ContinuousProfilingReportCommand.java ContinuousProfilingReportTask 处理 Agent 上报的连续 profiling 触发任务
AsyncProfilerTaskCommand.java (async-profiler 任务) 给 Java Agent 下发 async-profiler(JFR)任务
PprofTaskCommand.java (pprof 任务) 给 Go Agent 下发 pprof 任务
TraceIgnoreCommand.java (trace 忽略规则) 下发 trace 忽略规则

ProfileTaskCommand 详解(最典型的指令)

ProfileTaskCommand.java:26 实现的指令 ProfileTaskQuery(对应 Command.proto:43-53 的约定)字段:

  • TaskId:任务唯一 ID
  • EndpointName:要 profile 的 endpoint 名
  • Duration:profile 持续时长(秒)
  • MinDurationThreshold:最小耗时门槛,只有慢于这个值的请求才 profile
  • DumpPeriod:线程快照 dump 间隔(毫秒)
  • MaxSamplingCount:最大采样快照数
  • StartTime / CreateTime:任务起始/创建时间戳

序列化时(ProfileTaskCommand.java:92)把这些字段逐个转成字符串塞进 args;反序列化时(ProfileTaskCommand.java:54)遍历 args 按 key 还原。这正是"通用信封 + 约定 args"的落地方式。

配置发现的 UUID 优化

ConfigurationDiscoveryService.proto:40fetchConfigurations rpc 返回 Commands,期望指令是 ConfigurationDiscoveryCommand。注释(ConfigurationDiscoveryService.proto:33-39)说明:用一个保留 key UUID 作为配置指纹——Agent 缓存上次的 UUID,下次请求带上它(ConfigurationSyncRequest.uuid,ConfigurationDiscoveryService.proto:53),如果配置没变 OAP 返回空 Commands,减少流量。ConfigurationDiscoveryCommand.java:31 也定义了 UUID_CONST_NAME = "UUID"


采样协议与策略

采样在 OAP 后端做,不在 proto 里
SkyWalking 的 trace 采样主要在 OAP 后端完成,而不是 Agent 端全量决定。Agent 默认全量上报,OAP 用一个基于 traceId.hashCode() 的 0-9999 概率桶 + 慢/错误 trace 优先的策略,决定哪些 trace 真正落库。采样率可以按 service 维度配置,且支持动态下发。

采样策略本身不在 proto 协议里显式定义字段。SegmentObject 没有专门的"采样率"字段。采样是 OAP 收到 Segment 后的"丢弃/保留"决策,不改变 Agent 上报的格式。

什么是采样限流
线上流量一大,每秒几十万个 trace 全存下来,存储扛不住、查询也慢。“采样"就是只保留一部分 trace 落库——比如只留 30%,剩下 70% 收到即丢。关键是怎么决定"留哪些”:随机丢可能正好把出问题的慢请求丢了,所以 SkyWalking 的策略是"确定性桶 + 慢 trace 优先"——同一个 traceId 永远得到同一个采样号(要么全留要么全丢,保证链路完整),而且只要这个请求够慢(超过阈值),不管采样号是多少都留。

协议层与采样的唯一联系

  • SpanObject.isErrorstartTime/endTime(算 duration)是 OAP 采样决策的输入。
  • 采样率配置不是通过 Command 下发给 Agent,而是 OAP 侧自己加载 trace-sampling-policy-settings.yml(静态文件 + 动态配置),由 traceSamplingPolicy 这个 dynamic config key 驱动。

新手常见误解
“采样率是 OAP 通过 Command 下发给 Agent 的”。当前实现里不是——采样是 OAP 端的后置过滤。Command 下发的是 Profile 任务、配置发现等,不是 trace 采样率。

OAP 侧采样实现

路径修正
原文把 TraceSegmentSamplerTraceSamplingPolicyWatcher 都归到 trace/sampling/ 目录,实际不是。TraceSegmentSampler.javatrace/parser/listener/,TraceSamplingPolicyWatcher.javatrace/(sampling 子目录只放 SamplingPolicySamplingPolicySettingsSamplingPolicySettingsReader 三个纯数据类)。

核心类分散在两个目录:

TraceSegmentSampler(oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/parser/listener/TraceSegmentSampler.java:30):

// TraceSegmentSampler.java:33
public boolean shouldSample(SegmentObject segmentObject, int duration) {
    int sample = Math.abs(segmentObject.getTraceId().hashCode()) % 10000;
    String serviceName = segmentObject.getService();
    return traceSamplingPolicyWatcher.shouldSample(serviceName, sample, duration);
}

逻辑很简单:用 traceId.hashCode() 取模 10000 得到 sample(0-9999 的确定性桶),再交给 TraceSamplingPolicyWatcher.shouldSample(service, sample, duration) 判定。关键是同一条 trace 的所有 Segment 因为 traceId 相同,hash 结果相同,要么全采要么全丢——保证了链路完整性。如果用随机数,同一个请求的 A 段采了、B 段丢了,链路就断了。

SamplingPolicy(oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/sampling/SamplingPolicy.java:31):极简值对象,两个字段 rate(采样率,0-10000)和 duration(慢 trace 阈值,毫秒)。

SamplingPolicySettings(SamplingPolicySettings.java:30):

  • defaultPolicy:全局默认策略,构造时为 new SamplingPolicy(10000, -1),即 10000/10000=100% 采样,慢阈值 -1 表示不启用慢 trace 优先(SamplingPolicySettings.java:39)。
  • services:Map<String, SamplingPolicy>(SamplingPolicySettings.java:31),按 service 名覆盖默认策略。

采样精度
采样率精度是 1/10000,10000 表示 100%;慢 trace 阈值默认 -1(不采样慢 trace);单位毫秒。

TraceSamplingPolicyWatcher(oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/trace/TraceSamplingPolicyWatcher.java:37):继承 ConfigChangeWatcher,key 为 traceSamplingPolicy(:44)。判定逻辑(:73-113):

图2

  1. 取该 service 的 SamplingPolicy;没有则用默认(TraceSamplingPolicyWatcher.java:73-79)。
  2. 优先级:慢阈值 > 采样率(注释 :98-100)。意思是只要 duration 超了慢阈值就采,不管采样号。
  3. shouldSampleByDefault(sample, duration)(:89):duration >= 默认慢阈值 sample < 默认采样率 则采样。
  4. shouldSampleService(...)(:107-113):服务级慢阈值或服务级采样率命中则采样;服务级某项为 null 则回退到全局对应项。

withinRateRange 的实现就是 currentSample < policySample(:127-129)——sample 是 0-9999 的桶号,只要桶号小于采样率阈值就采。所以 rate=3000 意味着桶号 0-2999 采(30%),3000-9999 丢。

全量 vs 慢/错误 trace 的区别

模式 配置 行为
全量(默认) rate=10000 所有 trace 都采(sample < 10000 恒真)
采样模式 rate=3000 30% 概率采(sample < 3000 才采),其余丢弃省存储
慢 trace 优先 duration=500(ms) 无论是否命中采样率,只要耗时 ≥500ms 就采——保证慢请求的链路不丢

错误 trace
SpanObject.isError=true 不会被采样器直接豁免(采样器只看 duration 和 sample),但 OAP 在 isSizeLimited 等场景对错误 span 有保留逻辑。未确认错误 trace 是否有独立采样豁免分支。

配置来源与动态更新

  • 静态文件:trace-sampling-policy-settings.yml,由 SamplingPolicySettingsReader(SamplingPolicySettingsReader.java:35)用 SnakeYAML 解析。结构(:61-86):顶层 default: {rate, duration} + services: [{name, rate, duration}, ...]
  • 动态更新:TraceSamplingPolicyWatcher 注册为 traceSamplingPolicy 配置的 watcher(:44),当动态配置(如 ZooKeeper/Apollo/配置文件热更)下发新 YAML 字符串时,notify()(:50-58) → activeSetting()(:136-141) → parseFromYml()(:164-175)重新解析并替换 samplingPolicySettings(:143-151)。解析失败则保留上一份配置,不会回退到默认。

Compat 协议

Compat 是什么
*Compat.proto 是"旧版 proto 包名"的兼容服务定义,让使用旧版 skywalking.v3 包名(无独立 package 声明、java_package 不同)的 Agent 仍能连上新版 OAP。OAP 侧用 *HandlerCompat 类把这些旧包名请求委托给新版 handler 处理,从而实现 proto 字段演进时旧 Agent 不断链。

各 Compat 文件的作用

Compat proto 对应主 proto java_package
language-agent/TracingCompat.proto Tracing.proto ...language.agent.v3.compat
language-agent/JVMMetricCompat.proto JVMMetric.proto ...language.agent.v3.compat
language-agent/MeterCompat.proto Meter.proto ...language.agent.v3.compat
language-agent/CLRMetricCompat.proto CLRMetric.proto ...language.agent.v3.compat
management/ManagementCompat.proto Management.proto ...management.v3.compat
profile/ProfileCompat.proto Profile.proto ...language.profile.v3.compat
browser/BrowserPerfCompat.proto BrowserPerf.proto ...language.agent.v3.compat

为什么需要 Compat

gRPC 服务发现基于全限定方法名
proto 的字段编号一旦发布就不能改(改了就破坏线上的 Agent)。但有时需要"演进服务定义"——比如调整 java_package、调整 service 方法集合。问题在于:gRPC 的服务发现基于全限定方法名(包含 package)。如果直接改主 proto 的 package,旧版 Agent(已编译进老包名)发出的请求方法名对不上新版 OAP 注册的方法名,直接断链。

Compat 的解法:保留一份"旧包名、旧 service 名"的 proto(option deprecated = true 标记废弃),让 OAP 同时注册新旧两套 gRPC 服务。旧 Agent 连旧服务名、新 Agent 连新服务名,OAP 内部都用同一套 handler 处理。

Compat proto 的写法特征

观察 TracingCompat.proto:

  • 没有 package skywalking.v3; 声明(默认包,模拟旧版无 package 的状态)。
  • option java_package = "...language.agent.v3.compat"(TracingCompat.proto:22),生成的 gRPC stub 类落到 compat 子包。
  • option deprecated = true(TracingCompat.proto:25):标记这是废弃的兼容层。
  • import "language-agent/Tracing.proto"(TracingCompat.proto:28):复用新版 message 定义(skywalking.v3.SegmentObject 等),只是 service 用旧包名包一层。
  • service 体里直接引用 skywalking.v3.SegmentObject / skywalking.v3.Commands(见 TracingCompat.proto:36-43),即 message 是共享的,只是 service 全限定名不同。

*HandlerCompat 如何把旧请求转新处理

每个 receiver 插件都有一个 *HandlerCompat 类,它继承旧包名生成的 gRPC 基类,但内部直接委托给新版 handler。以 TraceSegmentReportServiceHandlerCompat(oap-server/server-receiver-plugin/skywalking-trace-receiver-plugin/src/main/java/org/apache/skywalking/oap/server/receiver/trace/provider/handler/v8/grpc/TraceSegmentReportServiceHandlerCompat.java:30)为例:

// TraceSegmentReportServiceHandlerCompat.java:30
public class TraceSegmentReportServiceHandlerCompat
    extends TraceSegmentReportServiceGrpc.TraceSegmentReportServiceImplBase  // 旧包名 stub
    implements GRPCHandler {
    private final TraceSegmentReportServiceHandler delegate;  // 新版 handler

    public StreamObserver<SegmentObject> collect(StreamObserver<Commands> responseObserver) {
        return delegate.collect(responseObserver);  // 直接转发
    }
    public void collectInSync(SegmentCollection request, StreamObserver<Commands> responseObserver) {
        delegate.collectInSync(request, responseObserver);
    }
}

因为 Compat proto 复用了 skywalking.v3.SegmentObject(同一个生成类),新旧 handler 收到的 SegmentObject 是同一个 Java 类型,所以零成本转发——不需要任何字段转换。

Compat 不是协议转换器
Compat 不是"旧字段转新字段"的协议转换器,而是"旧 gRPC 服务名 → 新 handler"的桥接器。真正的字段演进兼容靠 proto3 的"新增字段不破坏旧 consumer"特性保证,Compat 只解决 service 全限定名变化的问题。


JVM/CLR/Meter/Log/Event 的 message 结构

JVMMetricCollection / JVMMetric(JVM 指标)

定义见 apm-protocol/apm-network/src/main/proto/language-agent/JVMMetric.proto。service 是 JVMMetricReportService.collect(JVMMetricCollection) returns (Commands)(:32)。

JVMMetricCollection(:37):repeated JVMMetric metrics + service + serviceInstance。一次上报一个实例的多个时间点指标。

JVMMetric(:43)字段:

字段 类型 含义
time int64 指标时间戳(毫秒)
cpu CPU CPU 使用率(CPU.usagePercent: double,见 Common.proto:40)
memory repeated Memory 内存(堆/非堆)
memoryPool repeated MemoryPool 内存池
gc repeated GC GC 统计
thread Thread 线程统计
clazz Class 类加载统计

Memory(:53):isHeap(是否堆)、initmaxusedcommitted(int64,字节)。

MemoryPool(:61):type(PoolType 枚举)+ init/max/used/committed。PoolType(:69)枚举了 CODE_CACHE/NEWGEN/OLDGEN/SURVIVOR/PERMGEN/METASPACE/ZHEAP 等 11 种 JVM 内存池类型。

GC(:83):phase(GCPhase:NEW/OLD/NORMAL,NORMAL 用于 ZGC 这类无新旧代之分的 GC)+ count + time

Thread(:96):liveCount、daemonCount、peakCount,以及各状态线程数(runnableStateThreadCount、blockedStateThreadCount、waitingStateThreadCount、timedWaitingStateThreadCount)。

Class(:107):loadedClassCount、totalUnloadedClassCount、totalLoadedClassCount。

CLRMetric(.NET CLR 指标)

定义见 CLRMetric.proto,结构对称于 JVM。

CLRMetricCollection(:36)与 CLRMetric(:42):time + cpu + gc(ClrGC) + thread(ClrThread)。

ClrGC(:49):Gen0/Gen1/Gen2 回收次数 + HeapMemory

ClrThread(:56):可用/最大 CompletionPort 线程数、可用/最大 Worker 线程数。

MeterData(自定义 Meter)

定义见 Meter.proto。service MeterReportService 有两个 rpc:

  • collect(stream MeterData)(:32):每个 stream 作为 MAL 引擎的一个完整输入数据集,client 应每周期 onComplete 一次流。
  • collectBatch(stream MeterDataCollection)(:38):批量模式,流里每个 MeterDataCollection 都被当作 MAL 的一个完整输入。

MeterData(:80)字段:

字段 类型 含义
metric oneof singleValue(MeterSingleValue)或 histogram(MeterHistogram)
service string 服务名(流首元素设置)
serviceInstance string 实例名(流首元素设置)
timestamp int64 上报时间(流首元素设置)

Label(:43):name + value,metric 的标签维度。

MeterSingleValue(:60):name + repeated Label labels + value: double,单一数值型指标。

MeterHistogram(:70):name + labels + repeated MeterBucketValue values,直方图。

MeterBucketValue(:49):bucket(桶下界,double)+ count(桶内计数,int64)+ isNegativeInfinity(是否负无穷,为 true 时 bucket 值无效)。桶上界由"下一个 MeterBucketValue 的 bucket"决定,最后一个桶上界为正无穷。

当前 Meter 只有两种类型
当前版本的 Meter.protoMeterData.metric 的 oneof(Meter.proto:82-84)只有 singleValuehistogram 两种,无 mMultiIntValue 字段。MultiIntValuesHolder 是 OAP server-core 里 metrics 存储层的概念(用于多 int 值 metrics 如 percentile),与 Meter 上报协议无关。

MeterDataCollection(:94):repeated MeterData meterData,用于 collectBatch。

LogData(日志)

定义见 logging/Logging.proto。service LogReportService.collect(stream LogData) returns (Commands)(:32)。

LogData(:42)字段:

字段 类型 含义
timestamp int64 日志时间(毫秒,可选;不设则 OAP 用接收时间)
service string 服务名(必填;流式上报中非首元素可复用前一个非空值)
serviceInstance string 实例名(可选)
endpoint string endpoint(可选)
body LogDataBody 日志内容(必填)
traceContext TraceContext 关联的 trace 上下文(可选)
tags LogTags 标签(可选,供 OAP 搜索/分析)
layer string 服务/实例所属 layer(9.0.0+,缺省则 OAP 设为 general)

LogDataBody(:75):type(字符串,匹配 OAP 侧 analyzer)+ oneof content:TextLog.text / JSONLog.json / YAMLLog.yaml

TraceContext(:103):traceId + traceSegmentId + spanId——当 Agent 把 trace ID 注入日志文本时,用这个把日志和链路关联起来。

LogTags(:113):repeated KeyStringValuePair data

Event(事件)

定义见 event/Event.proto。service EventService.collect(stream Event) returns (Commands)(:30)。注释(:31-34):一个事件通常调两次 collect——一次开始一次结束,用同一个 UUID 关联;若起止时间都已知(如从第三方系统导入)则只调一次。

Event(:38)字段:

字段 类型 含义
uuid string 事件唯一 ID,关联开始/结束
source Source 事件发生源
name string 事件名(如 RebootUpgrade)
type Type Normal=0 / Error=1,影响 UI 颜色
message string 一行简述(不建议塞详细日志/堆栈)
parameters map<string,string> message 里的参数
startTime int64 起始时间(毫秒,必填)
endTime int64 结束时间(毫秒,可空表示未结束)
layer string 所属 layer(9.0.0+ 必填)

Source(:82):service + serviceInstance + endpoint,按粒度选择性必填(注释 :79-81:仅 service 事件只需 service;instance 事件需 service+serviceInstance;endpoint 事件需 service+endpoint)。


其他域 message 速览

  • Management(Management.proto):InstanceProperties(service+serviceInstance+properties+layer,上报实例自定义属性)、InstancePingPkg(service+serviceInstance+layer,保活心跳)。
  • BrowserPerf(BrowserPerf.proto):BrowserPerfData(页面级性能:redirectTime/dnsTime/ttfbTime/tcpTime/transTime/domAnalysisTime/fptTime/domReadyTime/loadPageTime/resTime/sslTime/ttlTime/firstPackTime/fmpTime 等毫秒级字段);BrowserErrorLog(错误日志,含 category 枚举 ajax/resource/vue/promise/js/unknown、grade、message、line、col、stack、errorUrl、firstReportedError);BrowserWebVitalsPerfData(FMP、CLS、LCP);BrowserResourcePerfData(资源加载);BrowserWebInteractionsPerfData(INP)。浏览器场景里 serviceVersion 对应后端的 Instance 概念、pagePath 对应 Endpoint 概念(注释 :56,61)。
  • Service Mesh(service-mesh.proto):ServiceMeshMetrics oneof httpMetrics/tcpMetrics;HTTPServiceMeshMetric 含 source/dest 服务名+实例、endpoint、latency、responseCode、status、protocol、detectPoint、tlsMode、internalErrorCode、Envoy 内部请求/响应延迟(纳秒);TCPServiceMeshMetric 额外有 receivedBytes/sentBytes。返回值是空 MeshProbeDownstream(:133)而非 Commands,是少数例外。
  • eBPF accesslog(ebpf/accesslog.proto):EBPFAccessLogMessage(node 信息 + connection + kernelLogs + protocolLog),含 L2/L3/L4 网络栈纳秒级延迟指标、HTTP 协议解析、trace 信息;返回空 EBPFAccessLogDownstream
  • eBPF profiling(ebpf/profiling/):EBPFProfilingData oneof onCPU/offCPU(Profile.proto:48),栈元数据区分 PROCESS_KERNEL_SPACE/PROCESS_USER_SPACE;ContinuousProfilingReport(Continuous.proto:53)含触发原因 ContinuousProfilingCause(CPU/线程数/系统负载/HTTP 错误率/HTTP 平均响应时间阈值),目标 task oneof onCPU/offCPU/network。
  • eBPF process(ebpf/profiling/Process.proto):EBPFProcessProperties oneof hostProcess/k8sProcess,含 EBPFProcessEntityMetadata(layer+serviceName+instanceName+processName+labels)。
  • async-profiler(AsyncProfiler.proto,package skywalking.v10):AsyncProfilerData(metaData + oneof errorMessage/content(JFR 二进制));AsyncProfilingStatus 枚举 PROFILING_SUCCESS/EXECUTION_TASK_ERROR/TERMINATED_BY_OVERSIZE;collect 是双向流,OAP 可中途用响应控制停止(如文件超限)。
  • pprof(Pprof.proto,package skywalking.v10):结构与 async-profiler 对称,PprofData(metadata + errorMessage/content pprof 二进制),同样双向流。

附录:componentId 与 component-libraries.yml

SpanObject.componentId(Tracing.proto:153)是一个预定义数字 ID,代表本次 span 用的框架/技术栈。所有 ID 在 oap-server/server-starter/src/main/resources/component-libraries.yml 集中定义(component-libraries.yml:43 起,Unknown: id=0),如 Tomcat: id=1HttpClient: id=2Dubbo: id=3H2: id=4Mysql: id=5 等。

ID 一旦发布不可更改
注释(component-libraries.yml:17)强调:ID 一旦发布不可更改,只能追加且必须唯一,废弃的 ID 永久保留,否则会导致可视化和聚合错误。每个���件还声明 languages(哪些语言用)和 priority(0-100,越接近业务代码优先级越高,默认 50,用于决定同一 span 多组件叠加时哪个胜出)。Agent 上报时只传数字 ID,OAP 据此反查组件名,省去传输字符串开销。


关键结论汇总

  1. apm-protocol 是纯协议层:只定义 proto + 少量 Command 序列化工��类,不含任何接收/分析逻辑;接收在 receiver 插件,分析在 analyzer 模块。
  2. trace 模型是 Segment 优先:不是"一堆 span 平铺",而是"每进程一个 Segment 包含若干 span",跨进程靠 SegmentReference 拼接,这是 SkyWalking 区别于 Zipkin 的核心设计。
  3. Command 是反向控制的通用信封:command 名 + args 键值对,复用所有 rpc 的 returns (Commands) 通道,新增指令不改 proto。
  4. 采样在 OAP 后端做,不在 proto 里:基于 traceId.hashCode() % 10000 的确定性桶 + 慢 trace 优先,按 service 配置,走 OAP 动态配置体系而非 gRPC Command 下发。
  5. Compat 解决的是 gRPC 服务全限定名变化,不是字段转换:靠"旧包名 service + 共享 message + HandlerCompat 委托新版 handler"实现旧 Agent 不断链。
  6. 当前 Meter 只有 singleValue 和 histogram(Meter.proto:82-84),无 mMultiIntValue 字段。

关键文件速查

子系统 关键文件 路径
trace 协议 Tracing.proto apm-protocol/apm-network/src/main/proto/language-agent/Tracing.proto
Command Command.proto apm-protocol/apm-network/src/main/proto/common/Command.proto
公共类型 Common.proto apm-protocol/apm-network/src/main/proto/common/Common.proto
JVM JVMMetric.proto apm-protocol/apm-network/src/main/proto/language-agent/JVMMetric.proto
Meter Meter.proto apm-protocol/apm-network/src/main/proto/language-agent/Meter.proto
Log Logging.proto apm-protocol/apm-network/src/main/proto/logging/Logging.proto
Event Event.proto apm-protocol/apm-network/src/main/proto/event/Event.proto
eBPF accesslog.proto + profiling/*.proto apm-protocol/apm-network/src/main/proto/ebpf/
async-profiler AsyncProfiler.proto apm-protocol/apm-network/src/main/proto/asyncprofiler/AsyncProfiler.proto
pprof Pprof.proto apm-protocol/apm-network/src/main/proto/pprof/Pprof.proto
Compat 各 *Compat.proto apm-protocol/apm-network/src/main/proto/{language-agent,management,profile,browser}/*Compat.proto
Command Java 封装 BaseCommand + 各 *Command apm-protocol/apm-network/src/main/java/.../network/trace/component/command/
采样入口 TraceSegmentSampler oap-server/analyzer/agent-analyzer/.../provider/trace/parser/listener/TraceSegmentSampler.java
采样策略 watcher TraceSamplingPolicyWatcher oap-server/analyzer/agent-analyzer/.../provider/trace/TraceSamplingPolicyWatcher.java
采样策略数据类 SamplingPolicy / SamplingPolicySettings / SamplingPolicySettingsReader oap-server/analyzer/agent-analyzer/.../provider/trace/sampling/
componentId component-libraries.yml oap-server/server-starter/src/main/resources/component-libraries.yml

接下来

协议清楚了,OAP 怎么对运行中进程做更深的性能剖析?四种 profiling 体系怎么对比?详见 10-Profiling体系。

心智模型
把 apm-protocol 想成"Agent 和 OAP 之间的合同":proto 文件是合同条款(每个数据域一份),Segment 是"每进程打包上报的集装箱"(里面装 span),Command 是"OAP 塞在回程包裹里的指令条"(复用上报通道反向下发),采样是"OAP 收货后决定哪些集装箱入库的筛选规则"(基于 traceId 的确定性桶,慢件优先),Compat 是"旧版合同的复印件"(让还在用旧合同的 Agent 也能签收,OAP 内部统一处理)。