Appearance
第19讲:OAP初始化流程精讲,一眼看透SkyWalkingOAP骨架
在前面的课程中,我们重点介绍了 SkyWalking Agent 的工作原理和核心实现,包括 Agent 核心启动流程、插件实现代码增强的核心原理、核心 BootService 实现类的原理、Trace 概念在 SkyWalking 中的落地实现、收发 Trace 数据的核心实现。最后深入介绍了 Tomcat、Dubbo 等常用开源软件的插件实现,以及 toolkit-activation 工具箱的核心原理。
OAP 架构
从本课时开始,我们将开始介绍 SkyWalking OAP 服务。OAP 与 Agent 类似,也采用了微内核架构(Microkernel Architecture),如下图所示。
OAP 使用 ModuleManager(组件管理器)管理多个 Module(组件),一个 Module 可以对应多个 ModuleProvider(组件服务提供者),ModuleProvider 是 Module 底层真正的实现。
在 OAP 服务启动时,一个 Module 只能选择使用一个 ModuleProvider 对外提供服务。一个 ModuleProvider 可能支撑了一个非常复杂的大功能,在一个 ModuleProvider 中,可以包含多个 Service ,一个 Service 实现了一个 ModuleProvider 中的一部分功能,通过将多个 Service 进行组装集成,可以得到 ModuleProvider 的完整功能。
ApplicationConfiguration(应用配置对象) 负责管理整个 OAP 的配置信息,ApplicationConfiguration 中包含多个 ModuleConfiguration(组件配置对象) 。
ModuleConfiguration 负责管理一个 Module 的配置信息,ModuleConfiguration 与 Module 是一一对应关系。ModuleConfiguration 中包含多个 ProviderConfiguration(服务提供者配置对象)。ProviderConfiguration 负责管理一个 ModuleProvider 的配置信息,两者也是一一对应的关系。上面两组对象之间的映射关系,如下图所示:
在之前搭建 SkyWalking 源码环境的课时中我们看到,OAPServerStartUp 类的 main() 方法是整个 OAP 服务的入口方法,其核心逻辑如下:
js
public static void main(String[] args) {
// mode这个环境变量有两个可选值:init、no-init,其中init值表示初始化底层
// 的存储结构,例如,使用ES时会初始化其中的索引,使用数据库时会初始化表结构;
// no-init值表示不初始化上述存储结构
String mode = System.getProperty("mode");
RunningMode.setMode(mode);
// 创建ApplicationConfigLoader,加载配置文件
ApplicationConfigLoader configLoader =
new ApplicationConfigLoader();
ApplicationConfiguration applicationConfiguration =
configLoader.load();
// 创建ModuleManager,并根据配置初始化全部Module
ModuleManager manager = new ModuleManager();
manager.init(applicationConfiguration);
// 查找指定Module中的指定Service进行使用,后面会展开核心Module进行分析
manager.find(TelemetryModule.NAME).provider()
.getService(MetricsCreator.class).createGauge("uptime",
"oap server start up time", MetricsTag.EMPTY_KEY,
MetricsTag.EMPTY_VALUE)
.setValue(System.currentTimeMillis() / 1000d);
}
初始化配置
OAP 启动时会使用 ApplicationConfigLoader 加载 application.yml 配置文件,该文件位于 server-starter 子模块的 resources 目录下,如下图所示:
application.yml 配置文件中包含了整个 OAP 服务中全部 Module 的配置信息,后面在介绍一个 Module 的时候会展示相应的配置信息。
在 load() 方法中, ApplicationConfigLoader 会创建 ApplicationConfiguration 对象以及相关的 ModuleConfiguration、ProviderConfiguration 对象。ApplicationConfiguration 维护了一个 HashMap<String, ModuleConfiguration> 集合(modules 字段),记录了 Module 名称与相应配置对象。
在 ModuleConfiguration 中只维护了一个 HashMap<String, ProviderConfiguration> 集合(providers 字段)记录了 ModuleProvider 名称与相应配置对象。在 ProviderConfiguration 中只有一个 Properties 集合(properties 字段),存储了 application.yml 中 KV 配置信息。下图所示 core Module 对应的 ModuleConfiguration 以及它下面 default ModuleProvider 对应的 ProviderConfiguration:
完成 application.yml 配置文件的读取之后,会紧接着调用 overrideConfigBySystemEnv() 方法读取 System.getProperties() 集合,并覆盖从 application.yml 读取到的相应默认值。
ModuleManager
完成配置的初始化之后,OAPServerStartUp 会创建 ModuleManager 对象。ModuleManager 中的 loadedModules 字段负责管理加载的 Module:
java
// Key是Module的名称,Value是相应的ModuleDefine,ModuleDefine就是对
// Module的抽象
Map<String, ModuleDefine> loadedModules = new HashMap<>();
在其 init() 方法中首先会通过 SPI 的方式加载插件配置文件中定义的 Module 实现类和 ModuleProvider 实现类,并调用其 prepare() 方法执行一些准备操作,最后创建 BootstrapFlow 开始启动流程:
java
public void init(ApplicationConfiguration applicationConfiguration){
// 根据配置拿到所有Module的名称
String[] moduleNames = applicationConfiguration.moduleList();
// 通过SPI方式加载ModuleDefine接口和 ModuleProvider接口的实现
ServiceLoader<ModuleDefine> moduleServiceLoader =
ServiceLoader.load(ModuleDefine.class);
ServiceLoader<ModuleProvider> moduleProviderLoader =
ServiceLoader.load(ModuleProvider.class);
LinkedList<String> moduleList =
new LinkedList<>(Arrays.asList(moduleNames));
for (ModuleDefine module : moduleServiceLoader) {
for (String moduleName : moduleNames) {
if (moduleName.equals(module.name())) {
// 通过SPI可能加载很多ModuleDefine实现以及ModuleProvider实
// 现类,但是这里只初始化在配置文件中出现过的Module,并调用
// 其prepare()方法
ModuleDefine newInstance =
module.getClass().newInstance();
newInstance.prepare(this, applicationConfiguration
.getModuleConfiguration(moduleName),
moduleProviderLoader);
// 记录初始化的ModuleDefine对象
loadedModules.put(moduleName, newInstance);
moduleList.remove(moduleName);
}
}
}
isInPrepareStage = false;
// 初始化BootstrapFlow,具体的初始化逻辑后面展开分析
BootstrapFlow bootstrapFlow = new BootstrapFlow(loadedModules);
// 启动Module
bootstrapFlow.start(this);
// 启动流程结束之后会通知相关组件
bootstrapFlow.notifyAfterCompleted();
}
ModuleDefine
前文提到 Module 与 ModuleProvider 的一对多关系,所以 ModuleDefine 抽象类继承了 ModuleProviderHolder 接口,该接口中只定义了一个 provider() 方法,用于获取该 Module 当前使用的 ModuleProvider 对象。
通过对 ModuleManager 的分析我们知道,ModuleDefine 的实现类是通过 SPI 方式被加载到内存的,这些实现类分散在不同的插件模块中,下图展示了 OAP 服务中几个核心插件模块的 ModuleDefine 实现类:
上图 ModuleDefine 实现类后面的课程会逐个分析,先来看 ModuleDefine 抽象类本身,其中有两个字段:
java
// 当前Module的名称
private final String name;
// 属于该Module的所有ModuleProvider对象
private final LinkedList<ModuleProvider> loadedProviders = new LinkedList<>();
每个 Module 都有一个全局唯一的名称,通过前面对配置初始化的介绍我们了解到,ModuleManager 会通过 Module 名称在 application.yml 配置文件中查找相应的配置信息。下图展示了核心 Module 对应的名称:
在 ModuleDefine.prepare() 方法中,会查找与当前 Module 配置的 ModuleProvider 实现,然后记录到 loadedProviders 集合中。随后会继续调用这些 ModuleProvider 对象的 prepare() 方法,继续准备操作:
java
void prepare(ModuleManager moduleManager,
ApplicationConfiguration.ModuleConfiguration configuration,
ServiceLoader<ModuleProvider> moduleProviderLoader) {
for (ModuleProvider provider : moduleProviderLoader) {
// 这里只关心配置文件中指定的、与当前Module相关的ModuleProvider实现类
if (!configuration.has(provider.name())) {
continue;
}
if (provider.module().equals(getClass())) {
// 初始化ModuleProvider对象
ModuleProvider newProvider =
provider.getClass().newInstance();
newProvider.setManager(moduleManager);
// 设置ModuleProvider与 Module之间的关联关系
newProvider.setModuleDefine(this);
// 将ModuleProvider对象记录到loadedProviders集合
loadedProviders.add(newProvider);
}
}
// 检查:该Module没有任何关联的ModuleProvider,会在这里报错(省略该检查代码)
for (ModuleProvider moduleProvider : loadedProviders) {
// 前面读取配置信息时,ModuleProvider的配置信息是存储到ProviderConfig
// 之中的Properties集合之中,此处,每个ModuleProvider都会关联一个
// ModuleConfig对象,并ProviderConfig中的配置信息拷贝到ModuleConfig
// 对象中的相应字段,实现Properties到Java Bean的转换
copyProperties(moduleProvider.createConfigBeanIfAbsent(),
configuration.getProviderConfiguration(
moduleProvider.name()), this.name(),
moduleProvider.name());
// 调用ModuleProvider的prepare()方法,继续prepare流程
moduleProvider.prepare();
}
}
ModuleDefine.provider() 方法(继承自 ModuleProviderHolder 接口)不仅会返回底层的 ModuleProvider 实例,还会保证 loadedProviders 集合有且只有一个 ModuleProvider 实现存在,如下所示,这也是前文提到的每个 Module 只会选用一个 ModuleProvider 实现的原因:
java
public final ModuleProvider provider() throws {
if (loadedProviders.size() > 1 || loadedProviders.size() == 0) {
throw new xxxException("..."); // 抛异常
}
return loadedProviders.getFirst(); // 返回唯一的ModuleProvider实例
}
ModuleProvider
完成 Module 的初始化之后再来看 ModuleProvider。 ModuleProvider 下可以关联多个 Service(一对多的关系,每个 Service 实现了一个简单功能,多个 Service 组装起来就是 ModuleProvider 的全部功能),所以实现了 ModuleServiceHolder 接口。在 ModuleServiceHolder 接口中定义了两个方法:
java
public interface ModuleServiceHolder {
// 注册Service实例,当Service类型与service对象不匹配时,会报错
void registerServiceImplementation(
Class<? extends Service> serviceType, Service service);
// 获取指定类型的Service对象
<T extends Service> T getService(Class<T> serviceType) ;
}
在 ModuleProvider 中维护了一个 HashMap 集合(services 字段),其中记录了 Service 类型与相应 Service 对象之间的映射关系。ModuleProvider 对 ModuleServiceHolder 接口的实现就是读写该集合。
与 ModuleDefine 实现类相同,ModuleProvider 的实现类也是通过 SPI 方式被加载的,下图展示了 OAP 服务中几个核心插件模块的 ModuleProvider 实现类:
除了 ModuleServiceHolder 接口中的这两个关键方法之外,ModuleProvider 中还定义了一些通用的抽象方法:
- name() 方法:返回当前 ModuleProvider 的名称,该名称在同一个 Module 下是唯一的。例如,StorageModule 负责实现 OAP 的持久化存储功能,Module 名称为 "storage",具体依赖的底层存储可以是 ElasticSearch、H2 等,分别对应StorageModuleElasticsearchProvider、H2StorageProvider 两个 ModuleProvider 实现类,ModuleProvider 名称分别是 "elasticsearch" 和 "h2"。
- createConfigBeanIfAbsent() 方法: 返回当前 ModuleProvider 对应的 ModuleConfig 对象。ModuleConfig 是一个空的抽象类,其实现类都是用于存储配置信息的 Java Bean。下图展示了 OAP 服务中几个核心插件模块对应的 ModuleConfig 实现类:
结合 ModuleDefine、ModuleProvider 以及 ModuleConfig,整个存储模块的结构如下:
prepare() 方法:与 ModuleDefine 中的 prepare() 方法的功能相同,也是完成一些准备性的操作,不同的 ModuleProvider 实现类会执行不同的准备操作。例如,StorageModuleElasticsearchProvider 会准备 High Level Rest Client 来访问 ElasticSearch,而 H2StorageProvider 则会准备 JDBC Client 来访问 H2 数据库。
requiredModules() 方法:返回当前 ModuleProvider 依赖的 Module 名称。整个 OAP 服务会有多个 Module,Module 之间相对独立,但是也有一定的依赖关系,例如,trace-receiver-plugin 插件模块(用于接收 Trace 数据)就依赖了 CoreModule、ConfigurationModule 等基础 Module。
requiredCheck() 方法:在 ModuleDefine.service() 这个方法中会定义该 Module 提供的一系列 Service,例如,在 StorageModule 中就会提供 StorageDAO、IRegisterLockDAO 等等一系列 DAO。requiredCheck() 方法会在 ModuleProvider 启动之前,检查当前 services 集合中是否已经装载了这些 Service 的实现,从而保证 ModuleProvider 能够完整地提供 Module 的功能。
start() 方法:启动当前 ModuleProvider,其中会完成一些初始化操作和启动操作。不同的 ModuleProvider 实现类会执行不同的初始化操作。例如,StorageModuleElasticsearchProvider 会在 ElasticSearch 中初始化相关的索引,而 H2StorageProvider 则会在 H2 数据库中初始化后续使用的表结构。
notifyAfterCompleted() 方法:当全部 Module 完成检查、初始化并启动之后,OAP 会回调该方法,通知所有监听者,当前 ModuleProvider 开始对外提供服务。
一个 ModuleProvider 对外提供相对完整的功能,ModuleProvider 中的一个子功能可以有一个 Service 实现。Service 接口是个空接口,没有定义任何方法,有点类似于 java.io.Serializable 接口,只是一个简单的标记接口。
BootstrapFlow
通过前面 ModuleManager 的分析,我们得到了下面的调用链:
那 ModuleProvider 的相关检查方法以及 start() 方法是在哪里调用的呢?在 ModuleManager.init() 方法中可以得到答案 ------ BootstrapFlow。在 BootstrapFlow 构造方法中会根据 ModuleProvider 与 Module 之间的依赖关系,确定所有 ModuleProvider 的启动顺序,并记录到一个 LinkedList 集合中(startupSequence 字段)。
在 BootstrapFlow.start() 方法中会遍历 startupSequence 集合,逐个启动 ModuleProvider 实例:
java
void start(ModuleManager moduleManager) {
// 按照startupSequence集合的顺序启动所有ModuleProvider实例
for (ModuleProvider provider : startupSequence) {
// 检测当前ModuleProvider依赖的Module对象是否存在
String[] requiredModules = provider.requiredModules();
if (requiredModules != null) {
for (String module : requiredModules) {
if (!moduleManager.has(module)) { ... ... // 抛异常 }
}
}
// 检查当前ModuleProvider对象是否能提供其所属Module需要的Service
provider.requiredCheck(provider.getModule().services());
provider.start(); // 上述两项检查都通过之后,才能启动ModuleProvider
}
}
start() 方法完成启动之后,会紧接着调用 notifyAfterCompleted() 方法,通知全部 ModuleProvider 实例:全部 Module 已正常启动了,可以对外提供服务了。
最后,简单看一下 startupSequence 集合的生成原理。下图示例中,ModuleProvider a 依赖 Module B、Module C,ModuleProvider b 依赖 Module C、ModuleProvider c 依赖 Module D。
在 BootstrapFlow.makeSequence() 方法中,根据 ModuleProvider 对 Module 依赖关系确定 ModuleProvider 的启动顺序,保证每个 ModuleProvider 启动时,其依赖 Module 的 ModuleProvider 已经启动。上图示例的最终启动顺序如下:
makeSequence() 方法的具体实现是靠四层 for 循环实现的,没有什么高深的算法,这里不展开了。
总结
本课时首先介绍了 Skywalking OAP 服务的整体框架,OAP 与 Agent 类似,也是微内核+插件的架构,主要围绕 Module、 ModuleProvider、Service 等核心概念展开。然后介绍了 SkyWalking OAP 服务的启动流程,其中涉及 ModuleDefine、ModuleProvider 等核心组件的准备流程和初始化流程。