
上一篇【第34篇】TraceId的生成算法——一个ID背后的精巧工程下一篇【第36篇】OAP Server启动流程解析从一行命令到一个可观测大脑的诞生一、开篇为什么OAP需要一个模块化框架想象一下你去一家自助餐厅。餐厅老板告诉你今天有川菜师傅所以有麻辣口味明天川菜师傅休假换粤菜师傅掌勺口味就变成清淡的。更神奇的是你甚至可以自己带一个意大利面师傅来把某个档口变成意面专区——而且不需要重新装修厨房。这就是SkyWalking OAP Server的模块化框架想要达到的效果。OAPObservability Analysis Platform作为SkyWalking的大脑需要同时处理多种任务接收探针数据、分析指标、存储追踪数据、发送告警、管理集群等等。如果把这些功能全部硬编码在一起维护成本会像滚雪球一样越滚越大。更麻烦的是不同用户的部署环境千差万别——有人用Elasticsearch做存储有人用MySQL有人用ZooKeeper做集群协调有人在Kubernetes上跑有人需要对接企业微信告警有人则用钉钉。模块化框架的核心目标就是让OAP像乐高积木一样每个功能都是一块独立的积木你可以自由替换、增减而不会让整个城堡倒塌。----------------------------------------------------------------------- | OAP Server | | | | ------------ ------------ ------------ ------------ | | | 存储模块 | | 集群模块 | | 告警模块 | | 遥测模块 | | | | (ES/MySQL) | |(ZK/Nacos) | |(钉钉/企微) | | (Prometheus)| | | ------------ ------------ ------------ ------------ | | | | | | | | ------------ ------------ ------------ ------------ | | | 分析模块 | | 查询模块 | | 接收器模块 | | 配置模块 | | | ------------ ------------ ------------ ------------ | | | ----------------------------------------------------------------------- 图1OAP Server就是一个模块拼图二、核心概念Module与Provider的双簧戏模块化框架的设计围绕两个核心接口展开ModuleDefine和ModuleProvider。理解它们的关系是理解整个框架的钥匙。2.1 ModuleDefine模块的身份证ModuleDefine是一个抽象类每个模块都必须继承它。它定义了一个模块是什么publicabstractclassModuleDefine{// 模块的唯一名称比如 storage、cluster、coreprivatefinalStringname;// 模块提供的服务接口类型// 模块间通过Service接口进行依赖注入publicabstractClass?extendsService[]services();// 模块的实现选择器如果有的话// 为null表示普通模块非null表示可选择实现的模块publicModuleConfigcreateConfigBeanIfAbsent(){returnnull;}// 当这个模块只有一个Provider时是否必须要有ProviderpublicbooleansupportMultiImplement(){returnfalse;}}每个ModuleDefine的本质就是一句话“OAP有个叫XXX的能力槽位它对外提供这些服务接口”。2.2 ModuleProvider模块的具体实现如果ModuleDefine是身份证那ModuleProvider就是持证上岗的人。同一个模块可以有多个Provider就像保安岗位可以由张三站岗也可以由李四站岗但同一时间只能有一个人站岗。publicabstractclassModuleProvider{// 属于哪个模块privateModuleDefinemodule;// Provider的唯一名称privateStringname;// 依赖的其他模块服务privatefinalMapClass?extendsService,ServicerequiredServices;// 准备工作读取配置、建表等publicabstractvoidprepare()throwsModuleStartException;// 启动服务publicabstractvoidstart()throwsModuleStartException;// 所有模块启动完成后的回调publicabstractvoidnotifyAfterCompleted()throwsModuleStartException;// 注册自己的服务实现publicabstractServiceregisterServiceImplementation(Class?extendsServiceserviceType)throwsServiceNotProvidedException;}2.3 关系图解ModuleDefine (模块定义-身份证) | | 属于 v ------------------ | | ModuleProvider A ModuleProvider B (ES存储实现) (MySQL存储实现) | -------------- | | Service A Service B (IStorageDAO) (IModelInstaller) 图2ModuleDefine、ModuleProvider、Service三者关系关键理解一个ModuleDefine可以有多个ModuleProvider多实现可选一个ModuleProvider可以注册多个Service对外暴露多个服务接口模块之间通过requiredServices进行依赖注入三、application.yaml中的模块配置之美每个模块在application.yml中都有属于自己的配置区块。看一个典型的配置片段# application.yml - OAP Server模块配置core:selector:${SW_CORE:default}# 核心模块通常就一个实现default:# 核心模块的配置gRPCHost:${SW_CORE_GRPC_HOST:0.0.0.0}gRPCPort:${SW_CORE_GRPC_PORT:11800}restHost:${SW_CORE_REST_HOST:0.0.0.0}restPort:${SW_CORE_REST_PORT:12800}maxConcurrentCallsPerConnection:${SW_CORE_MAX_CONCURRENT:0}storage:# 存储模块selector:${SW_STORAGE:elasticsearch}# 选择器决定用哪个存储实现elasticsearch:# Elasticsearch实现的配置nameSpace:${SW_NAMESPACE:}clusterNodes:${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}protocol:${SW_STORAGE_ES_HTTP_PROTOCOL:http}user:${SW_ES_USER:}password:${SW_ES_PASSWORD:}# ...更多ES配置h2:# H2实现的配置默认用于Demodriver:${SW_STORAGE_H2_DRIVER:org.h2.jdbcx.JdbcDataSource}url:${SW_STORAGE_H2_URL:jdbc:h2:mem:skywalking-oap-db}user:${SW_STORAGE_H2_USER:sa}mysql:# MySQL实现的配置# ...MySQL配置cluster:# 集群模块selector:${SW_CLUSTER:standalone}# 选择器standalone/zk/nacos/k8sstandalone:# 单机模式-无需额外配置zookeeper:hostPort:${SW_CLUSTER_ZK_HOST_PORT:localhost:2181}sessionTimeout:${SW_CLUSTER_ZK_SESSION_TIMEOUT:40000}nacos:serviceName:${SW_SERVICE_NAME:SkyWalking_OAP_Cluster}hostPort:${SW_CLUSTER_NACOS_HOST_PORT:localhost:8848}# ...Nacos配置注意配置结构的美妙之处selector字段决定使用哪个Provider实现环境变量占位符如${SW_STORAGE:elasticsearch}让配置可以在运行时切换每个Provider的配置区块独立互不干扰四、模块依赖声明谁依赖谁模块之间不是孤岛。存储模块需要知道我存到哪里核心模块需要存储模块来持久化数据。依赖关系通过ModuleProvider中的requiredModules来声明。// 分析模块的实现类publicclassAnalyzerModuleProviderextendsModuleProvider{OverridepublicModuleConfigcreateConfigBeanIfAbsent(){returnnewAnalyzerModuleConfig();}// 声明依赖关系OverridepublicString[]requiredModules(){returnnewString[]{core,// 依赖核心模块storage,// 依赖存储模块cluster// 依赖集群模块};}Overridepublicvoidprepare()throwsModuleStartException{// 从依赖模块获取服务this.getManager().find(storage).provider().getService(IStorageDAO.class);this.getManager().find(cluster).provider().getService(ClusterModuleClient.class);}}依赖关系构成了一个有向无环图DAG框架在启动时会进行拓扑排序确保被依赖的模块先启动。----------- | Core | | (核心) | ---------- | -------------------- | | -----v----- ----v------ | Storage | | Cluster | | (存储) | | (集群) | ---------- ---------- | | -------------------- | ------v------ | Analyzer | | (分析引擎) | ------------ | ------v------ | Alarm | | (告警) | ------------- 图3模块依赖的有向无环图(DAG)五、模块管理器的初始化全流程一切的核心是ModuleManager。它就像一个交响乐团的指挥负责扫描所有可用的乐手模块确认每个乐手都能演奏配置正确安排排练顺序拓扑排序指挥演奏三阶段启动publicclassModuleManager{// 所有已加载的ModuleDefineprivatefinalMapString,ModuleDefineloadedModules;// 模块配置从application.yml解析而来privatefinalApplicationConfigurationapplicationConfiguration;// 初始化publicvoidinit(){// 第一阶段扫描模块loadModules();// ServiceLoader扫描所有的ModuleDefine// 第二阶段解析配置loadConfigurations();// 解析application.yml绑定到各模块配置// 第三阶段加载ProviderloadProviders();// 根据selector选择正确的Provider// 第四阶段拓扑排序sortProviders();// 按依赖关系排序// 第五阶段三阶段启动prepareAllModules();// prepare阶段startAllModules();// start阶段notifyAfterCompleted();// 所有模块启动完成通知}}初始化流程图----------------- | OAPServerBootstrap| | 启动入口 | ----------------- | v ----------------- | ModuleManager | | .init() | ----------------- | ---------------------------- | | v v ------------------- ------------------- | ServiceLoader扫描 | | 解析application.yml | | 所有ModuleDefine | | 构建配置对象 | ------------------- ------------------- | | ---------------------------- | v ---------------------------- | 根据selector为每个模块 | | 选择对应的ModuleProvider | ---------------------------- | v ---------------------------- | 根据requiredModules依赖声明 | | 进行拓扑排序 | ---------------------------- | v ---------------------------- | | v v v --------- --------- ----------- | prepare | | start | |notifyAfter | | (准备) |-| (启动) |-|Completed | | | | | |(完成通知) | ---------- ---------- ------------ 图4模块管理器初始化全流程六、深入源码ModuleDefine的实现案例让我们看一个真实的模块定义——存储模块StorageModule// 存储模块的核心定义publicclassStorageModuleextendsModuleDefine{publicstaticfinalStringNAMEstorage;publicStorageModule(){super(NAME);}OverridepublicClass?extendsService[]services(){// 存储模块暴露的服务接口ListClass?extendsServiceservicesnewArrayList();services.add(IStorageDAO.class);// 数据访问对象services.add(IModelInstaller.class);// 数据模型安装器services.add(IHistoryDeleteDAO.class);// 历史数据删除// 如果存储支持聚合查询if(storageConfig.isSupportAggregation()){services.add(IAggregationDAO.class);}returnservices.toArray(newClass[0]);}}再看看存储模块的Elasticsearch Provider是怎么注册这些服务的publicclassStorageModuleElasticsearchProviderextendsModuleProvider{publicStorageModuleElasticsearchProvider(){// 告诉框架我是storage模块的elasticsearch实现// 同时也声明了我的直接依赖addRequiredModuleDependency(core);// 运行时依赖core模块启动}OverridepublicStringname(){returnelasticsearch;}OverridepublicClass?extendsModuleDefinemodule(){returnStorageModule.class;}OverridepublicModuleConfigcreateConfigBeanIfAbsent(){returnnewStorageConfig();// ES的配置类}Overridepublicvoidprepare()throwsModuleStartException{// 1. 读取配置StorageConfigconfig(StorageConfig)getModuleConfig();// 2. 初始化ES客户端this.elasticSearchClientnewElasticSearchClient(config.getClusterNodes(),config.getUser(),config.getPassword());// 3. 注册服务this.registerServiceImplementation(IStorageDAO.class,newStorageEsDAO(elasticSearchClient));this.registerServiceImplementation(IModelInstaller.class,newStorageEsInstaller(elasticSearchClient,config));this.registerServiceImplementation(IHistoryDeleteDAO.class,newHistoryDeleteEsDAO(elasticSearchClient));}Overridepublicvoidstart()throwsModuleStartException{// 连接ES集群验证连接elasticSearchClient.connect();}OverridepublicvoidnotifyAfterCompleted()throwsModuleStartException{// 所有模块启动完成后安装数据模型创建索引等IModelInstallerinstallergetService(IModelInstaller.class);installer.install();}}七、配置类的设计类型安全的魔法每个模块都有自己的配置类继承自ModuleConfig。框架通过反射将YAML配置自动绑定到这些Java对象上// 存储模块的配置类publicclassStorageConfigextendsModuleConfig{// Getter/Setter 省略privateStringnamespace;privateStringclusterNodes;privateStringprotocolhttp;privateStringuser;privateStringpassword;// 批量处理配置privateintbulkActions2000;privateintbulkSize20;privateintflushInterval10;// 秒// 索引配置privateintindexShardsNumber2;privateintindexReplicasNumber0;// TTL配置privateintrecordDataTTL7;// 追踪数据保留7天privateintminuteMetricsDataTTL90;// 分钟指标保留90天privateinthourMetricsDataTTL45;// 小时指标保留45天privateintdayMetricsDataTTL45;// 天指标保留45天}YAML配置中的值会自动映射到这些字段上框架使用了SnakeYAML和反射技术实现。配置可以使用环境变量覆盖这为容器化部署带来了极大的灵活性。八、实战理解模块的热插拔能力模块化框架最强大的特性就是实现可替换性。以存储模块为例# 场景A开发环境用H2内存数据库exportSW_STORAGEh2 ./bin/oapService.sh# 场景B测试环境用ElasticsearchexportSW_STORAGEelasticsearchexportSW_STORAGE_ES_CLUSTER_NODESes-test:9200 ./bin/oapService.sh# 场景C生产环境用Elasticsearch集群exportSW_STORAGEelasticsearchexportSW_STORAGE_ES_CLUSTER_NODESes1:9200,es2:9200,es3:9200 ./bin/oapService.sh同一个OAP发行包同一个启动脚本只改变环境变量就能适配完全不同的基础设施。这就是模块化框架的魅力——它把选择权交还给了运维者。九、总结OAP Server的模块化框架是整个SkyWalking可观测平台的地基。理解了这个地基你就能明白为什么SkyWalking可以如此灵活地适应各种部署场景。核心要点回顾ModuleDefine定义模块的身份和能力ModuleProvider提供具体实现selector机制让运行时切换实现成为可能依赖声明拓扑排序保证模块按正确顺序启动三阶段启动prepare→start→notifyAfterCompleted提供精细的生命周期管理配置类自动绑定让YAML配置直接驱动Java对象下一篇我们将深入到OAP Server的启动流程看看ModuleManager是如何一步步把零件组装成一台精密运转的机器的。上一篇【第34篇】TraceId的生成算法——一个ID背后的精巧工程下一篇【第36篇】OAP Server启动流程解析从一行命令到一个可观测大脑的诞生