模块系统与启动流程
这一篇解决什么
OAP 后端有几十个功能模块(核心分析、存储、集群、告警、查询、各种 receiver 插件……),它们怎么拼成一个可运行的整体?答案是一套自研的"模块系统":ModuleDefine(模块契约)+ModuleProvider(模块实现)+ 标准 Java SPI 发现 + 拓扑排序启动。这篇拆解这套骨架,然后跟一遍 OAP 的启动main流程,最后看CoreModule这个"大脑"里装了什么。理解了这一篇,后面所有模块的代码都能对号入座。
为什么自研模块系统
SkyWalking 没用 Spring,而是自己写了一套极简的模块框架。原因是三个工程诉求 Spring 不好满足:
- 解耦:每个模块独立,对外只暴露
Service接口,实现可替换。存储可以是 BanyanDB、ES、JDBC,配置一切换就行,调用方代码不动。 - 可插拔:同一模块可以有多种实现(provider),运行时只装载配置选中的那一个。这让 OAP 能按部署形态裁剪——单机用 H2,集群用 BanyanDB,互不干扰。
- 有序启动:模块之间有依赖,需要拓扑排序后按顺序启动,还要能检测循环依赖。Spring 的
@DependsOn能表达依赖但不会在启动期就帮你拓扑排序和报循环错。
整套代码在 oap-server/server-library/library-module/src/main/java/org/apache/skywalking/oap/server/library/module/,只有约 10 个类,非常精简。
核心抽象:四个角色

三个角色一句话
Service:空接口(Service.java:26),是所有跨模块能力的根。模块对外承诺"我能提供这些 Service"。ModuleDefine:模块的"契约",声明name()和services()(对外暴露哪些 Service 接口)。抽象类,子类如CoreModule。ModuleProvider:模块的"实现",一个模块可以有多种 provider,但运行时只装一个。生命周期分prepare() → start() → notifyAfterCompleted()三段。ModuleManager:全局唯一总管,负责 SPI 加载、拓扑排序、驱动启动,并提供find(moduleName)给运行期跨模块查 Service。
ModuleDefine · 契约
oap-server/server-library/library-module/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleDefine.java:31
关键方法:
name()(:46)返回模块名,如"core"、"storage"。services()(:53)是抽象方法,子类必须重写,返回这个模块对外暴露的所有Service接口的 Class 数组。这是模块的"对外契约清单"。prepare(...)(:62)是模块的 prepare 阶段入口:遍历所有候选 Provider,选出与配置匹配且module()等于当前模块的那个 Provider 装载进来。一个模块只能装一个 Provider,否则抛DuplicateProviderException(:79),然后创建配置 Bean、调用provider.prepare()。
对新手来说要记住:ModuleDefine 是接口/契约,ModuleProvider 是实现。比如 CoreModule 只声明 services(),真正干活的是 CoreModuleProvider。
ModuleProvider · 实现
oap-server/server-library/library-module/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleProvider.java:31
生命周期三段是新手最需要理解的核心:

三阶段能干什么、不能干什么
prepare()(:84):只做"不依赖其他模块"的初始化,主要是registerServiceImplementation(...)把 Service 实现注册进来。此时不能访问别的模块(find会抛错)。start()(:89):此时所有模块都已 prepare 完,可以跨模块互操作,比如getManager().find(CoreModule.NAME).provider().getService(XxxService.class)。notifyAfterCompleted()(:94):所有模块都 start 完才执行,用于启动定时器、gRPC/HTTP server 等"最后才能做的事"。
声明方法:
name()(:50)Provider 名,如"default",对应application.yml里 selector 选中的名字。module()(:55)声明本 Provider 属于哪个ModuleDefine。newConfigCreator()(:61)创建配置 Bean。requiredModules()(:99)声明依赖哪些模块——拓扑排序就靠它。
registerServiceImplementation(...)(:105)是 final 方法,把 Service 实现塞进内部 services Map。requiredCheck(...)(:120)在 start 阶段校验:注册的 Service 数量必须和 ModuleDefine.services() 声明的完全一致,多了少了都报错。
模块之间怎么注入依赖
不是 Spring 那种字段注入,而是服务定位器(Service Locator)模式。

每个 Provider 持有 getManager()(ModuleProvider.java:43),需要别的模块时直接:
getManager().find(CoreModule.NAME).provider().getService(ConfigService.class)
查找链是:ModuleManager.find("core") → ModuleDefine(同时也是 ModuleProviderHolder)→ .provider() → ModuleProvider(同时也是 ModuleServiceHolder)→ .getService(XxxService.class)。
这套 ModuleDefineHolder 接口被传给几乎所有 Worker,让 Worker 也能跨模块找 Service。所以"依赖注入"在 SkyWalking 里实质是"持有 ModuleManager 引用,按名 + 接口查找"。
为什么用服务定位器而不是 Spring 的字段注入?因为后者要在构造期就拿到依赖,而 OAP 的模块是按拓扑序逐个启动的,先启动的模块此刻后面的还没就绪。服务定位器把"找依赖"推迟到运行时调用那一刻,自然避开了这个时序问题。代价是调用方要主动 find,没有 Spring @Autowired 那种声明式简洁——但对一个要按 selector 动态裁剪 provider 的系统,这个代价是值得的。
SPI 机制:标准 Java SPI
模块系统用的是标准 JDK java.util.ServiceLoader,不是自定义 SPI。
证据在 oap-server/server-library/library-module/src/main/java/org/apache/skywalking/oap/server/library/module/ModuleManager.java:50-51:
ServiceLoader.load(ModuleDefine.class);
ServiceLoader.load(ModuleProvider.class);
注册文件在各 jar 的 META-INF/services/ 下,文件名即接口全限定名。
server-core 的 SPI 注册
oap-server/server-core/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine内容列出了StorageModule、ClusterModule、CoreModule、QueryModule、AlarmModule、ExporterModule六个模块定义类。同目录的
...ModuleProvider文件只有一行:org.apache.skywalking.oap.server.core.CoreModuleProvider。这意味着 server-core 这个 jar 声明了自己实现了哪些模块(6 个 Define),但只自带了 1 个 Provider 实现(CoreModuleProvider)。其他模块(Storage/Cluster/Query/Alarm/Exporter)的 Provider 由各自的插件 jar 提供,靠 selector 决定装哪个。
模块发现的两种顺序不要混
- 发现顺序:由
ServiceLoader遍历META-INF/services决定,这是无序的。- 启动顺序:由
BootstrapFlow.makeSequence基于requiredModules()做拓扑排序决定,与发现顺序无关。模块之间正确的先后关系靠依赖图保证,不靠配置文件里写的顺序。
启动流程:从 main 到模块全就绪
入口极简。oap-server/server-starter/src/main/java/org/apache/skywalking/oap/server/starter/OAPServerStartUp.java:22 的 main 只调 OAPServerBootstrap.start()。
真正的活在 oap-server/server-starter/src/main/java/org/apache/skywalking/oap/server/starter/OAPServerBootstrap.java:38 的 start()。
完整启动时序

OAPServerBootstrap.start()(OAPServerBootstrap.java:38-69)的步骤:
new ModuleManager("Apache SkyWalking OAP")(:39)创建内核。- 读系统属性
mode并RunningMode.setMode(mode)(:42-43)。 new ApplicationConfigLoader(bootingParameters)(:45),写入 Running Mode、Version(:47-48)。configLoader.load()得到ApplicationConfiguration(:51)。manager.init(applicationConfiguration)(:52)——这是整个模块启动流程。- 启动成功后
ServerStatusService.bootedNow(...)(:54-57)标记启动完成时间,这对MetricsPersistentWorker的缓存策略很关键。 - 若
RunningMode.isInitMode()(:59)则只做初始化(建表)然后System.exit(0)(:61)。 - 任何异常
log.error后System.exit(1)(:63-65)。 finally块打印bootingParameters表格(:67)。
RunningMode · 运行模式
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/RunningMode.java:26
通过 -Dmode=init / -Dmode=no-init 控制:
isInitMode()(:44):init 模式只做存储初始化(建表/建 measure),建完即退出,用于首次部署或 schema 升级。isNoInitMode()(:53):no-init 模式启动但不做存储初始化。
在 CoreModuleProvider.notifyAfterCompleted()(CoreModuleProvider.java:489)处生效:if (!RunningMode.isInitMode()) 才真正 grpcServer.start() 等网络服务。
application.yml 怎么解析
配置加载在 oap-server/server-starter/src/main/java/org/apache/skywalking/oap/server/starter/config/。
ApplicationConfigLoader.load()(ApplicationConfigLoader.java:64)两步:
loadConfig(:72):用 SnakeYAML 把 classpath 下的application.yml读成Map<String, Map<String, Object>>,结构是模块名 → (provider 名 → 配置)。overrideConfigBySystemEnv(:122):遍历System.getProperties(),按moduleName.providerName.settingKey格式覆盖 YAML 配置(overrideModuleSettings:161),支持 int/String/long/boolean 类型转换。
selector 机制
selectConfig(ApplicationConfigLoader.java:128-159)是关键:每个模块下有selector字段,值为某个 provider 名(或-表示禁用)。
- 解析
selector(支持${...}占位符替换系统属性,:138)。removeIf把不等于 selector 值的所有 provider 配置删掉(:141)。- 删完为空且 selector 不是
-,抛ProviderNotFoundException(:148)。- selector 是
-,直接把这个模块从配置里移除(:156)——这就是"禁用某模块"的官方写法。
BootstrapFlow · 拓扑排序
BootstrapFlow 是 package-private 类(BootstrapFlow.java:28),只在 ModuleManager.init 内部用。
makeSequence(BootstrapFlow.java:56-120)的核心:

反复把"所有依赖都已在序列中"的 Provider 加入 startupSequence,直到全部排完。若一轮下来没有任何 Provider 能排进去(说明互相依赖),抛 CycleDependencyException(:114)。还会校验依赖的模块是否真的加载了(:62-68,缺失抛 ModuleNotFoundException)。
排好序后,start(:40-48)按拓扑序对每个 Provider 先 requiredCheck(校验 Service 注册齐全),再 provider.start()。notifyAfterCompleted(:50-54)按同一序调每个 Provider 的收尾方法。
CoreModule · 大脑里装了什么
oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModule.java:80 是整个 OAP 的"大脑",services()(:88)列出约 60 个 Service 接口。

按职责分组(CoreModule.java 行号):
- 配置类:
ConfigService(:90)、DownSamplingConfigService(:92)、NamingControl(:93,命名长度控制)、EndpointNameGroupService(:118)。 - 流处理骨架:
IWorkerInstanceGetter/IWorkerInstanceSetter(:99-100,Worker 注册与查找,用于集群 RPC 路由)、MeterSystem(:102,MAL 入口)。 - 服务端接口:
GRPCHandlerRegister/HTTPHandlerRegister(addServerInterface:173,注册 gRPC/HTTP handler)。 - 接收入口:
SourceReceiver(addReceiverInterface:191,所有 source 数据的统一入口)。 - 内部服务:
ModelRegistry、IModelManager、ModelManipulator(存储模型注册表)、RemoteClientManager、RemoteSenderService(集群消息分发)。 - 缓存:
NetworkAddressAliasCache;ProfileTaskCache/AsyncProfilerTaskCache/PprofTaskCache(:136-148)。 - 查询服务(面向 UI):
TopologyQueryService、MetricsQueryService、TraceQueryService、LogQueryService、MetadataQueryService、AggregationQueryService、AlarmQueryService、TopNRecordsQueryService、BrowserLogQueryService、EventQueryService、TagAutoCompleteQueryService、RecordQueryService、HierarchyQueryService等。 - OAL runtime:
OALEngineLoaderService(:151)。 - 其他:
SpanListenerManager(:97,trace span 监听器)、CommandService(:116)、HierarchyService(:117)。
这些 Service 的默认实现全部在 CoreModuleProvider.prepare()(CoreModuleProvider.java:185)里通过 registerServiceImplementation 注册。
CoreModuleProvider 的三阶段实样
以 CoreModuleProvider 为真实样本,看三阶段各做什么:
prepare / start / notifyAfterCompleted 的真实分工
prepare()(CoreModuleProvider.java:185):注册全部 Service 实现、建GRPCServer/HTTPServer但不启动、建RemoteClientManager、扫描 Scope 注解、设置 StreamProcessor 参数——全是"自我准备"。
start()(CoreModuleProvider.java:417):把RemoteServiceHandler加进 gRPC、调receiver.scan()扫描 Dispatcher、启动ClusterCoordinator、注册动态配置 watcher——开始跨模块协作。
notifyAfterCompleted()(CoreModuleProvider.java:481):grpcServer.start()、httpServer.start()、remoteClientManager.start()、PersistenceTimer.INSTANCE.start(...)、DataTTLKeeperTimer、CacheUpdateTimer——真正对外服务。
这个三段式分工是 SkyWalking 所有 Provider 的通用模式:prepare 备料、start 接线、notifyAfterCompleted 通电。
异常体系
启动期的校验失败都是可读异常,方便定位:
| 异常 | 触发场景 |
|---|---|
ProviderNotFoundException |
selector 指定的 provider 不存在 |
DuplicateProviderException |
一个模块装了多个 Provider |
ServiceNotProvidedException |
Provider 注册的 Service 数与声明不符 |
ModuleNotFoundException |
requiredModules 声明的依赖没加载 |
CycleDependencyException |
模块间循环依赖 |
关键文件速查
| 子系统 | 关键类 | 路径 |
|---|---|---|
| 模块系统 | ModuleDefine | oap-server/server-library/library-module/.../module/ModuleDefine.java |
| ModuleProvider | 同目录 ModuleProvider.java |
|
| ModuleManager | 同目录 ModuleManager.java |
|
| BootstrapFlow | 同目录 BootstrapFlow.java |
|
| Service/ModuleConfig | 同目录 Service.java / ModuleConfig.java |
|
| 启动入口 | OAPServerStartUp | oap-server/server-starter/.../starter/OAPServerStartUp.java |
| OAPServerBootstrap | 同目录 OAPServerBootstrap.java |
|
| ApplicationConfigLoader | oap-server/server-starter/.../starter/config/ApplicationConfigLoader.java |
|
| RunningMode | oap-server/server-core/.../core/RunningMode.java |
|
| server-core | CoreModule | oap-server/server-core/.../core/CoreModule.java |
| CoreModuleProvider | 同目录 CoreModuleProvider.java |
|
| SPI 注册文件 | — | oap-server/server-core/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.{ModuleDefine,ModuleProvider} |
接下来
骨架搞清楚了,下一步看数据怎么进来:02-数据采集层-Receiver。
贯穿全篇的心智模型
把ModuleDefine想成"插座规格"(声明能插什么),ModuleProvider想成"插头"(具体实现),ModuleManager想成"配电箱"(管哪些插座通电、按什么顺序通电)。Service就是插座上流出来的电。这个比喻贯穿后面所有模块——你看到的每一个插件(storage-banyandb、cluster-zookeeper、receiver-trace)都是一个插头,插进对应的插座。