Apache SkyWalking:从遥测接收到查询的 OAP 架构解析
SkyWalking 面向微服务和云原生系统,把探针采集的链路、指标、日志、事件与性能剖析数据汇入 OAP(Observability Analysis Platform)后端。OAP 既是协议入口,也是分析与聚合运行时:接收器把不同格式翻译为统一领域对象,分析流水线计算指标和拓扑,可插拔存储承接写入与查询,Horizon UI、Grafana 或 API 客户端再从查询面读取结果。
SkyWalking 架构的关键不在“支持多少协议”,而在模块化内核怎样让接收、分析、存储和查询彼此解耦。本文固定到源码提交 25c74a8,重点阅读 OAP 的启动装配、原生追踪接收、OAL/MAL 分析、存储接口和查询路径;各语言 Agent、Rover、BanyanDB 与 Horizon UI 均是独立仓库,正文只说明外部项目与 OAP 的边界,不把外部项目的实现算入本次覆盖。
GitHub 仓库信息
| 信息 | 内容 |
|---|---|
| 项目标题 | Apache SkyWalking |
| 项目描述 | 面向云原生分布式系统的开源可观测性平台,提供遥测数据采集、分析、聚合、查询与可视化能力。 |
| GitHub 仓库 | apache/skywalking |
| 官网地址 | skywalking.apache.org |
| 主要开发语言 | Java 96.94%、Shell 1.86%、ANTLR 0.43% |
| 开源许可证 | Apache-2.0 |
| 最近代码更新 | 2026-09-12(GitHub pushed_at,核对日期:2026-09-13) |
四个角色,一条数据主线
官方架构把系统分成 Probe、Platform backend、Storage 和 UI 四部分。Probe 可以是语言 Agent、服务网格、eBPF Rover,也可以是 OpenTelemetry、Zipkin、Prometheus 等生态组件。当前仓库主要实现 Platform backend,也就是 OAP;存储和 UI 通过明确接口连接,具体产品可以独立演进。
正在准备渲染...
查看源码
flowchart LR
P[探针与开放协议] --> R[Receiver 接收器]
R --> A[Analyzer 分析与聚合]
A --> W[流式 Worker]
W --> S[(Storage 接口)]
S --> B[(BanyanDB / Elasticsearch / JDBC)]
Q[GraphQL / PromQL / TraceQL / LogQL] --> S
U[Horizon UI / Grafana / API 客户端] --> Q
C[配置、集群、告警、导出] -.协作.-> A这里有一个容易误解的边界:SkyWalking 不是把原始遥测简单转存后再交给前端计算。OAP 在写入前完成语义建模、聚合、拓扑关系生成与规则计算,因此存储插件面对的是 OAP 定义的数据模型和 DAO 契约,而不是任意协议的原始报文。
仓库结构与职责
skywalking/
├── apm-protocol/ # OAP 使用的网络协议与生成代码
├── oap-server/
│ ├── server-starter/ # 进程入口与配置加载
│ ├── server-library/ # 模块系统、网络等基础库
│ ├── server-core/ # 领域模型、分析、存储和查询契约
│ ├── server-receiver-plugin/ # SkyWalking、OTel、Zipkin 等接收器
│ ├── analyzer/ # trace、meter、log、event 等分析器
│ ├── server-storage-plugin/ # BanyanDB、Elasticsearch、JDBC 实现
│ ├── server-query-plugin/ # GraphQL、PromQL、TraceQL、LogQL
│ ├── server-cluster-plugin/ # 集群发现与节点协作
│ ├── server-alarm-plugin/ # 告警规则和通知
│ └── server-telemetry/ # OAP 自身遥测
├── dist-material/ # 发布包脚本和默认配置
├── docker/ # OAP + 存储 + Horizon UI Compose
└── docs/ # 架构、构建、部署与扩展文档根 pom.xml 默认组合 apm-protocol、oap-server、BOM 与发行包;oap-server/pom.xml 又列出接收、分析、存储、查询、集群、告警和遥测等子模块。这比目录数量更重要:OAP 的扩展边界由 Maven 模块与运行时模块系统共同维持。
| 想理解的能力 | 首要入口 | 向下阅读 |
|---|---|---|
| 启动与配置 | server-starter | library-module 的模块生命周期 |
| Trace 接入 | skywalking-trace-receiver-plugin | agent-analyzer 与 core analysis |
| 指标规则 | oal-grammar、oal-rt | 生成的 metrics 代码与 worker |
| 多生态指标 | otel-receiver-plugin | meter-analyzer 的 MAL 脚本 |
| 数据落盘 | server-core/storage | BanyanDB、Elasticsearch、JDBC provider |
| 查询与 UI | query-graphql-plugin | core query service 与 Storage DAO |
模块生命周期:OAP 的真正骨架
Java 入口 OAPServerStartUp.main 只调用 OAPServerBootstrap.start()。后者读取运行模式和应用配置,再把配置交给 ModuleManager。ModuleManager 使用 Java ServiceLoader 发现 ModuleDefine 与 ModuleProvider,按配置为每个模块选中 provider,随后建立依赖有向图并启动。
正在准备渲染...
查看源码
sequenceDiagram
participant Main as OAPServerStartUp
participant Boot as OAPServerBootstrap
participant Config as ApplicationConfigLoader
participant MM as ModuleManager
participant Flow as BootstrapFlow
participant Provider as ModuleProvider
Main->>Boot: start()
Boot->>Config: load()
Config-->>Boot: ApplicationConfiguration
Boot->>MM: init(configuration)
MM->>MM: ServiceLoader 发现模块与 provider
MM->>Provider: prepare()
MM->>Flow: 按 requiredModules 建立启动顺序
Flow->>Provider: start()
Flow->>Provider: notifyAfterCompleted()
Flow->>Provider: notifyBootCompleted()
Boot->>Boot: 标记 ServerStatus 已启动ModuleProvider 的阶段划分有实际约束:prepare 处理不依赖其他模块的初始化,start 才允许模块互操作,全部模块完成通知后才进入 notifyBootCompleted。源码注释明确说明核心网络端口在最后阶段开放,避免请求进入一个仍在启动的模块。provider 同时声明 requiredModules(),错误选择、缺失模块或循环依赖会在启动期失败,而不是等到第一条遥测到达才暴露。
这套设计的收益是同一个逻辑模块可以有多个实现,例如 Storage 选择 BanyanDB、Elasticsearch 或 JDBC。代价是启动行为同时由配置、SPI 注册和依赖图决定;排查“模块不存在”时只看 YAML 不够,还要核对发行包是否包含对应 provider。
从一条 Trace 到可查询结果
不同协议的入口类并不相同,但主链可以稳定地拆成四段:协议接收、语义分析、流式聚合、存储写入。下表把文章覆盖的代表性链路和重要分支放在一起。
| 链路 | 入口 | 核心交接 | 输出或副作用 | 关键分支 |
|---|---|---|---|---|
| OAP 启动 | OAPServerStartUp | 配置 → ModuleManager → BootstrapFlow | 模块就绪并开放端口 | provider 缺失、依赖环、init 模式 |
| 原生 Trace | gRPC Trace receiver | segment → trace analyzer → source/worker | 指标、拓扑、链路记录 | 采样、过滤、跨节点转发 |
| OTel/Meter | OTel receiver | 协议转换 → MAL/meter analyzer | 统一指标模型 | 规则匹配、未知指标、标签维度 |
| 日志 | log receiver | LAL 解析 → 日志分析/采样 | 日志记录与派生指标 | 解析失败、丢弃、trace 关联 |
| 查询 | GraphQL/PromQL 等 | query service → Storage DAO | 面板或 API 响应 | 存储能力、时间范围、表达式错误 |
接收与分析不是同一层
Receiver 负责协议和传输边界,例如原生 Trace、OpenTelemetry、Zipkin、Envoy、Zabbix 或 Telegraf。分析器负责 SkyWalking 语义:把数据归入 Layer、Service、Service Instance、Endpoint 和 Process 等实体,并触发指标与关系计算。这样新增协议通常不必复制完整分析逻辑;反过来,修改领域模型会影响多个接收器和查询面,需要更谨慎地评估兼容性。
原生 Trace 的源码交接可以从接收器取得的 ISegmentParserService 开始。接收器把 gRPC 报文还原为 SegmentObject 后调用 SegmentParserServiceImpl.send;该方法为本次 segment 创建 TraceAnalyzer 并进入 doAnalysis。TraceAnalyzer 依次驱动已注册 listener:入口、出口和 RPC listener 生成服务与端点关系,segment listener 形成 Trace 记录,TraceSegmentSampler 决定是否保留完整 segment。各 listener 最终把 core source 交给 SourceReceiver,再由对应 stream processor 进入聚合或持久化。这样,采样可以减少完整 Trace 存储,却不会被误解为简单地关闭所有服务指标。
OAL(Observability Analysis Language)用于声明服务、实例、端点等维度上的聚合指标;MAL(Meter Analysis Language)让 Prometheus、OpenTelemetry、Telegraf 等指标走脚本化的统一处理。ANTLR grammar、运行时和生成代码共同把规则变成执行逻辑。规则带来可配置性,也把一部分错误从 Java 编译期移动到规则装载和运行期,调试时应先确认规则是否成功加载,再追踪 worker 与存储。
MAL 的代表性链路更直接:接收器形成同一次采集的 SampleFamily 集合,规则对应的 Analyzer 在构建阶段用 ANTLR4 与 Javassist 编译表达式、解析所需样本和标签,并向 MeterSystem 注册指标;运行阶段只选取表达式引用的样本,应用可选标签过滤,执行表达式,再把结果交给 MeterSystem 存储。运行时规则更新采用“先全部编译、全部成功后再注册”的两阶段处理,避免同一规则文件后半段失败时留下部分 schema。
日志链由 LAL(Log Analysis Language)控制。规则执行产生 LALOutputBuilder 后,RecordSinkListener 先用日志 metadata 补齐服务、Layer、时间和 Trace 上下文,再调用 builder 的 complete(SourceReceiver)。默认 builder 输出 Log,自定义 builder 还能输出 Envoy access log、数据库慢语句或采样 Trace;规则没有产生有效 builder 时,listener 直接返回,不会制造空记录。
写入与查询通过契约相遇
分析产生的指标、关系、日志和 Trace 由 server-core/storage 定义的存储接口承接,具体 provider 实现索引、批写和查询 DAO。查询插件并不直接了解 Elasticsearch 或 BanyanDB 的客户端细节,而是从 StorageModule 取得对应查询服务。query-graphql-plugin 把 schema 和 resolver 接到 core query service,service 再向当前 Storage provider 索取 DAO;GraphQL 是 Horizon UI 的主要公共查询面,PromQL、TraceQL、LogQL 和 Zipkin API 则服务兼容工具和已有生态。查询失败应按“请求语法 → query service 参数 → DAO 能力 → 存储状态”的顺序定位。
这也是第二个重要转折:更换存储并非只替换连接字符串。接口屏蔽了客户端实现,但索引模型、保留周期、查询能力、迁移与容量规划仍由具体后端决定。生产选型必须同时看查询语义和运维约束。
集群、失败与扩展边界
OAP 可以同时承担 Receiver 与 Aggregator,也可以拆分角色。Receiver 节点接收数据后,需要根据服务或实体键把分析任务路由到适当节点;集群 provider 负责成员发现,remote 模块承接跨节点传输。默认架构不强制引入消息队列,减少组件数量,但峰值削峰、跨区域网络和故障隔离需要由部署架构另行解决。
最值得关注的失败点并不只在入口:
- 配置选择了发行包中不存在的 provider,模块准备阶段即失败;循环依赖由启动图检测。
- 存储不可用会影响批写和查询,健康检查只能说明 OAP 端口可连接,不能证明后端已能正常读写。
- 接收成功不等于数据一定出现在面板;采样、过滤、规则不匹配、实体时间窗口和存储延迟都可能改变结果。
- 查询语言插件共享底层数据,但语义并不完全等价;迁移仪表盘前应核对支持的函数、标签和时间范围。
扩展 OAP 能力时,优先判断新能力属于哪一层:新遥测格式进入 receiver;新聚合进入 OAL/MAL 或 analyzer;新持久化后端实现 Storage provider 与 DAO;新查询协议进入 query plugin。跨过这些边界直接调用具体 provider,短期容易,长期会破坏替换能力。
获取源码、构建与调试
官方构建说明要求 Git、Maven 3.6+ 和 JDK 11/17/21/25,并强调仓库含 Git submodule,不建议直接拿 GitHub 自动生成的源码压缩包进行编译。以下命令来自固定提交的官方文档,本次没有执行:
git clone --recurse-submodules https://github.com/apache/skywalking.git
cd skywalking
git checkout 25c74a80860dc09d6281526345650ceee46dcf37
./mvnw clean package -Dmaven.test.skip成功后发行包应出现在 dist/。只构建后端和发行包可使用 ./mvnw package -Pbackend,dist。IDE 首次导入前,官方建议先执行 ./mvnw compile -Dmaven.test.skip=true,因为 gRPC、Protobuf、FlatBuffers 与 ANTLR 会生成源码;没有生成目录时,大量“类不存在”通常是构建准备不完整,而非业务源码损坏。
调试 OAP 启动可按以下顺序缩小范围:
- 从
OAPServerBootstrap确认配置文件是否成功解析。 - 在
ModuleManager.prepare查看模块与 provider 是否被 SPI 发现。 - 根据
requiredModules()检查依赖图,而不是任意调整模块顺序。 - 端口开放后再发送测试遥测,并同时检查 OAP 自身遥测和存储日志。
Docker Compose 最短体验
固定提交的 docker/docker-compose.yml 是官方维护路径,提供 elasticsearch 与 banyandb 两个 profile,均由 OAP、存储和 Horizon UI 组成。下面使用更贴近 SkyWalking 当前生态的 BanyanDB 路径;docker/.env 中的镜像 tag 应固定到与目标发布匹配的版本,不能把示例的 latest 直接用于生产。
在仓库的 docker/ 目录运行:
docker compose --profile banyandb config
docker compose --profile banyandb up -d
docker compose --profile banyandb ps官方配置把遥测入口映射到 localhost:11800,OAP 查询端口为 12800,管理端口为 17128,Horizon UI 在 http://localhost:8080,默认登录信息来自只读挂载的 horizon.yaml。首次启动 BanyanDB 并安装 schema 时,OAP 可能需要 60–120 秒;Compose 的健康检查为此设置了启动宽限期。
这条 Compose 路径只完成基础设施启动验收:先确认存储与 OAP 均为 healthy,再打开 Horizon UI 并确认登录页可访问。产生真实遥测还需要选择与应用语言相符的官方 Agent,并把后端地址设为 127.0.0.1:11800;Agent 的安装命令与版本由对应语言仓库维护,不在本文拼接一个可能过期的示例。完成 Agent 接入后,以 Horizon UI 出现预期服务、实例和 Trace 作为业务成功信号。重建 OAP 容器后数据是否仍在,取决于外部存储的持久化配置。仓库演示 Compose 的 BanyanDB 命令把数据写在容器 /tmp 路径且未声明持久卷,因此这份 Compose 适合本地预览,不构成生产持久化方案。
停止演示可用 docker compose --profile banyandb down。不要在需要保留数据的部署中盲目追加 -v;生产环境还需独立设计存储备份、版本兼容、认证、TLS 和网络暴露。本文只完成了官方文件与配置的静态核对,没有运行 Compose、拉取镜像或验证恢复与升级。
仓库内的 Agent Skills
.claude/skills/ 下有 9 个以贡献任务命名的 Skill 文件:compile、generate-classes、test、package、run-e2e、ci-e2e-debug、license、gh-pull-request 和 new-monitoring-feature。目录位置和名称说明这些文件属于仓库内部维护入口,而不是 OAP 运行时组件;本次没有采集 Skill 正文,因此不进一步推断触发条件、命令或执行结果。
OAP 的 Maven 模块和 Compose 均未把 .claude/skills 纳入运行路径。本文不评价这些 Skill 的运行效果或跨 Agent 兼容性。
设计取舍与适用场景
| 维度 | SkyWalking 的选择 | 带来的收益 | 需要承担的成本 |
|---|---|---|---|
| 数据模型 | 以服务、实例、端点、进程和 Layer 建模 | APM 拓扑与故障定位更直接 | 接入协议要映射到统一语义 |
| 扩展方式 | 模块 + provider + ServiceLoader | 存储、集群和接收实现可替换 | 配置、SPI 与依赖图增加排障层次 |
| 分析时机 | OAP 流式分析后写入 | 查询侧负担更可控 | 规则错误会影响后续数据,需要治理变更 |
| 生态兼容 | 原生协议并接入 OTel、Prometheus、Zipkin 等 | 可复用既有采集体系 | 各查询/标签语义不能假定完全一致 |
| 默认拓扑 | 不强制消息队列 | 组件更少、入门部署更短 | 大规模削峰与故障隔离需额外设计 |
SkyWalking 适合希望把 APM 拓扑、分布式追踪、多来源指标与日志关联放在同一平台,并愿意运营 OAP 与外部存储的团队。只需要采集和转发遥测时,OpenTelemetry Collector 更轻;以指标与 PromQL 为中心时,Prometheus/Grafana 通常更直接;以日志或搜索为中心时,Elastic Stack 的数据模型更自然。选择依据应是主要排障问题和运维能力,不是协议清单长度。
常见问题
构建时出现大量生成类缺失:先确认 submodule 完整,再执行官方的 Maven compile。Protobuf、gRPC、FlatBuffers 和 ANTLR 输出未生成时,IDE 索引无法代表项目真实可编译状态。
OAP 启动时报 module/provider missing:核对 application.yml 选择项和发行包内容,检查 provider 的 SPI 注册及 requiredModules()。不要用复制 JAR 的方式掩盖版本不一致。
容器 healthy,但 UI 没有数据:端口探针只证明进程可连接。继续检查 Agent 的服务名与后端地址、接收器是否启用、OAP 是否完成 schema 初始化、存储是否可写,以及数据是否落在查询时间范围。
更换存储后历史数据不见了:Storage provider 可替换不等于数据自动迁移。切换前应按目标后端的官方迁移与备份方案处理索引、schema 和保留策略,并用服务列表、指标和 Trace 查询做业务验收。
后续源码阅读路径
要新增协议,从对应 receiver provider 的 prepare/start 与 handler 开始,追到 core source 和 analyzer;要新增指标,先读 OAL/MAL grammar、运行时和生成代码,再找存储模型;要排查面板查询,从 GraphQL schema 或兼容查询插件进入 query service,最后落到 Storage DAO。修改模块装配或生命周期前,应先读 ModuleManager、BootstrapFlow 和 ModuleProvider,因为一次看似局部的启动调整可能改变全部 provider 的就绪顺序。
总结
OAP 的骨架是带依赖顺序的模块生命周期,数据主线是 receiver 将协议对象交给 analyzer,再由 source、worker 和 Storage provider 完成聚合与持久化,查询插件沿 core query service 和 DAO 读回结果。模块与 provider 让协议、存储和查询实现可以替换,同时也把配置、SPI 注册、规则编译和存储 schema 变成必须共同治理的运行条件。
本次源码抽样已经覆盖启动、Trace、MAL、LAL、存储和查询边界,但没有逐个分析所有 receiver、存储 provider 与集群实现,也没有执行构建或容器。读者可以按实际目标选择下一步:数据不入库时沿 Trace 或 meter 链向下查,查询异常时从 query plugin 反向追到 DAO,开发新扩展时先守住 receiver、analyzer、storage 与 query 的模块边界。