Module loading mechanism
Basic overview
Module is a mechanism provided by Skywalking in OAP to manage features. Through the Module mechanism, you can easily define modules and provide multiple implementations. You can choose any implementation in the configuration file.
For details about modules, see Backend Setup and Configuration Vocabulary
The class diagram
Skywalking module management related functions in the org. Apache. Skywalking. Oap. Server. If the module package.
It can be seen from the class diagram that the Skywalking module mechanism is roughly divided into the following modules:
- Module configuration:
ApplicationConfiguration
、ModuleConfiguration
、ProviderConfiguration
- PS: Exactly
application.yml
Three-tier structure: module -> module implementation -> configuration of a module implementation.
- PS: Exactly
- Module definition class:
ModuleDefine
- The module provides classes:
ModuleProvider
- Services:
Service
- Management:
ModuleManager
- Some helper classes
ModuleDefineHolder
: Interface used by the module management class to search for modulesModuleProviderHolder
: a module defines the interface that the class needs to implement and provides the function of getting the service class of the moduleModuleServiceHolder
: Module provides the interface that the class needs to realize, and provides the functions of registering service implementation and obtaining service objectsModuleConfig
: module configuration class, module definition class willProviderConfiguration
Mapped toModuleConfig
ApplicationConfigLoader
:ApplicationConfiguration
The helper class willapplication.yml
Configuration file loaded into memory, Settingsselector
The correspondingProvider
Configuration information of
Class diagram source file: Skywalk-module.uml
The source code parsing
ModuleDefine
package org.apache.skywalking.oap.server.library.module;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Properties;
import java.util.ServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Module definition
public abstract class ModuleDefine implements ModuleProviderHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleDefine.class);
// Module actual
private ModuleProvider loadedProvider = null;
private final String name;
public ModuleDefine(String name) {
this.name = name;
}
/ / module name
public final String name(a) {
return name;
}
// The implementation class can define the service classes provided by the module
public abstract Class[] services();
/**
* Run the prepare stage for the module, including finding all potential providers, and asking them to prepare.
*
* @param moduleManager of this module
* @param configuration of this module
* @throws ProviderNotFoundException when even don't find a single one providers.
*/
// In the preparation phase, locate the ModuleProvider object corresponding to the Configuration class and initialize it
void prepare(/ / module management object ModuleManager ModuleManager, / class/module configuration ApplicationConfiguration ModuleConfiguration configuration, ServiceLoader
moduleProviderLoader)
throws ProviderNotFoundException, ServiceNotProvidedException, ModuleConfigException, ModuleStartException {
// Find the ModuleProvider object corresponding to the Configuration class
for (ModuleProvider provider : moduleProviderLoader) {
if(! configuration.has(provider.name())) {continue;
}
if (provider.module().equals(getClass())) {
if (loadedProvider == null) {
loadedProvider = provider;
loadedProvider.setManager(moduleManager);
loadedProvider.setModuleDefine(this);
} else {
throw new DuplicateProviderException(this.name() + " module has one " + loadedProvider.name() + "[" + loadedProvider.getClass().getName() + "] provider already, " + provider.name() + "[" + provider.getClass().getName() + "] is defined as 2nd provider."); }}}if (loadedProvider == null) {
throw new ProviderNotFoundException(this.name() + " module no provider found.");
}
// Copy the config file of the supplied class into the ModuleConfig object
LOGGER.info("Prepare the {} provider in {} module.", loadedProvider.name(), this.name());
try {
copyProperties(
loadedProvider.createConfigBeanIfAbsent(),
configuration.getProviderConfiguration(loadedProvider.name()),
this.name(),
loadedProvider.name()
);
} catch (IllegalAccessException e) {
throw new ModuleConfigException(this.name() + " module config transport to config bean failure.", e);
}
// The module provides the object to be ready
loadedProvider.prepare();
}
// Use reflection to copy properties
private void copyProperties(ModuleConfig dest, Properties src, String moduleName, String providerName) throws IllegalAccessException {
if (dest == null) {
return; } Enumeration<? > propertyNames = src.propertyNames();while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
Class<? extends ModuleConfig> destClass = dest.getClass();
try {
Field field = getDeclaredField(destClass, propertyName);
field.setAccessible(true);
field.set(dest, src.get(propertyName));
} catch (NoSuchFieldException e) {
LOGGER.warn(propertyName + " setting is not supported in " + providerName + " provider of " + moduleName + " module"); }}}private Field getDeclaredField(Class
destClass, String fieldName) throws NoSuchFieldException {
if(destClass ! =null) {
Field[] fields = destClass.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals(fieldName)) {
returnfield; }}return getDeclaredField(destClass.getSuperclass(), fieldName);
}
throw new NoSuchFieldException();
}
// Get the Provider object corresponding to the module definition
@Override
public final ModuleProvider provider(a) throws DuplicateProviderException, ProviderNotFoundException {
if (loadedProvider == null) {
throw new ProviderNotFoundException("There is no module provider in " + this.name() + " module!");
}
returnloadedProvider; }}Copy the code
ModuleProviderHolder
package org.apache.skywalking.oap.server.library.module;
// The module provides a holding interface. Through this interface, you can obtain the Service holding interface corresponding to the module Provider object and obtain the Service object corresponding to the module Provider object
public interface ModuleProviderHolder {
// Get the module supplied object
ModuleServiceHolder provider(a) throws DuplicateProviderException, ProviderNotFoundException;
}
Copy the code
ModuleProvider
package org.apache.skywalking.oap.server.library.module;
import java.util.HashMap;
import java.util.Map;
import lombok.Setter;
// A module provides an abstract class from which all modules provide classes need to inherit
// A module definition can be configured to provide classes for multiple modules, by switching in application.yml
public abstract class ModuleProvider implements ModuleServiceHolder {
// Module manager
@Setter
private ModuleManager manager;
// The module defines the object
@Setter
private ModuleDefine moduleDefine;
// The module provides the map of the corresponding service object
private final Map<Class<? extends Service>, Service> services = new HashMap<>();
public ModuleProvider(a) {}protected final ModuleManager getManager(a) {
return manager;
}
// Get the name of the service provider implementation class
public abstract String name(a);
// Define the module definition class implemented by the module provider
public abstract Class<? extends ModuleDefine> module(a);// Create the module definition configuration object
public abstract ModuleConfig createConfigBeanIfAbsent(a);
// Prepare (initialize things that are not related to other modules)
public abstract void prepare(a) throws ServiceNotProvidedException, ModuleStartException;
// Start phase (this phase can interoperate modules)
public abstract void start(a) throws ServiceNotProvidedException, ModuleStartException;
// Notification phase after completion (executed after all modules have successfully started)
public abstract void notifyAfterCompleted(a) throws ServiceNotProvidedException, ModuleStartException;
// The names of other modules on which the module depends
public abstract String[] requiredModules();
// Register the service implementation class
@Override
public final void registerServiceImplementation(Class<? extends Service> serviceType, Service service) throws ServiceNotProvidedException {
if (serviceType.isInstance(service)) {
this.services.put(serviceType, service);
} else {
throw new ServiceNotProvidedException(serviceType + " is not implemented by "+ service); }}// Ensure that all services are implemented
void requiredCheck(Class<? extends Service>[] requiredServices) throws ServiceNotProvidedException {
if (requiredServices == null)
return;
for (Class<? extends Service> service : requiredServices) {
if(! services.containsKey(service)) {throw new ServiceNotProvidedException("Service:" + service.getName() + " not provided"); }}if(requiredServices.length ! = services.size()) {throw new ServiceNotProvidedException("The " + this.name() + " provider in " + moduleDefine.name() + " moduleDefine provide more service implementations than ModuleDefine requirements."); }}// Get the service implementation object
@Override
public @SuppressWarnings("unchecked")
<T extends Service> T getService(Class<T> serviceType) throws ServiceNotProvidedException {
Service serviceImpl = services.get(serviceType);
if(serviceImpl ! =null) {
return (T) serviceImpl;
}
throw new ServiceNotProvidedException("Service " + serviceType.getName() + " should not be provided, based on moduleDefine define.");
}
ModuleDefine getModule(a) {
return moduleDefine;
}
String getModuleName(a) {
returnmoduleDefine.name(); }}Copy the code
ModuleConfig
package org.apache.skywalking.oap.server.library.module;
// Module configuration class
public abstract class ModuleConfig {}Copy the code
ModuleServiceHolder
package org.apache.skywalking.oap.server.library.module;
// Module services hold interfaces
public interface ModuleServiceHolder {
// Register the service implementation object
void registerServiceImplementation(Class<? extends Service> serviceType, Service service) throws ServiceNotProvidedException;
// Get the service implementation object
<T extends Service> T getService(Class<T> serviceType) throws ServiceNotProvidedException;
}
Copy the code
Service
package org.apache.skywalking.oap.server.library.module;
// Service interface
public interface Service {}Copy the code
ModuleDefineHolder
package org.apache.skywalking.oap.server.library.module;
// The module defines the holding interface
public interface ModuleDefineHolder {
// Check whether the module exists
boolean has(String moduleName);
// Get the module definition object by the module name
ModuleProviderHolder find(String moduleName) throws ModuleNotFoundRuntimeException;
}
Copy the code
ModuleManager
package org.apache.skywalking.oap.server.library.module;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.ServiceLoader;
// Module management class, manage module life cycle
public class ModuleManager implements ModuleDefineHolder {
// Whether all modules have passed the preparation stage
private boolean isInPrepareStage = true;
// All loaded modules define object maps
private final Map<String, ModuleDefine> loadedModules = new HashMap<>();
// Initialize all configured modules
public void init(ApplicationConfiguration applicationConfiguration) throws ModuleNotFoundException, ProviderNotFoundException, ServiceNotProvidedException, CycleDependencyException, ModuleConfigException, ModuleStartException {
// Get the module name in the configuration class
String[] moduleNames = applicationConfiguration.moduleList();
// SPI loads all module definition objects
ServiceLoader<ModuleDefine> moduleServiceLoader = ServiceLoader.load(ModuleDefine.class);
// SPI loads all module supplied objects
ServiceLoader<ModuleProvider> moduleProviderLoader = ServiceLoader.load(ModuleProvider.class);
// All modules defined in the configuration class are prepared
LinkedList<String> moduleList = new LinkedList<>(Arrays.asList(moduleNames));
for (ModuleDefine module : moduleServiceLoader) {
for (String moduleName : moduleNames) {
if (moduleName.equals(module.name())) {
module.prepare(this, applicationConfiguration.getModuleConfiguration(moduleName), moduleProviderLoader);
loadedModules.put(moduleName, module); moduleList.remove(moduleName); }}}// The preparation phase is over
isInPrepareStage = false;
if (moduleList.size() > 0) {
throw new ModuleNotFoundException(moduleList.toString() + " missing.");
}
// Determine the initialization order of modules according to the requiredModules method in the module provision object (dependent modules are loaded first)
BootstrapFlow bootstrapFlow = new BootstrapFlow(loadedModules);
// All modules enter the startup phase
bootstrapFlow.start(this);
// All modules enter the completion notification phase
bootstrapFlow.notifyAfterCompleted();
}
// Check whether the module exists
@Override
public boolean has(String moduleName) {
returnloadedModules.get(moduleName) ! =null;
}
// Get the module definition object by the module name
@Override
public ModuleProviderHolder find(String moduleName) throws ModuleNotFoundRuntimeException {
assertPreparedStage();
ModuleDefine module = loadedModules.get(moduleName);
if (module! =null)
return module;
throw new ModuleNotFoundRuntimeException(moduleName + " missing.");
}
// Assert whether the preparation phase is still underway, and if so, throw an exception
private void assertPreparedStage(a) {
if (isInPrepareStage) {
throw new AssertionError("Still in preparing stage."); }}}Copy the code
BootstrapFlow
package org.apache.skywalking.oap.server.library.module;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Determine the initialization order of modules according to the requiredModules method in the module provision object (dependent modules are loaded first)
class BootstrapFlow {
private static final Logger LOGGER = LoggerFactory.getLogger(BootstrapFlow.class);
private Map<String, ModuleDefine> loadedModules;
// Modules that are sorted by dependency provide a list of objects
private List<ModuleProvider> startupSequence;
BootstrapFlow(Map<String, ModuleDefine> loadedModules) throws CycleDependencyException, ModuleNotFoundException {
this.loadedModules = loadedModules;
startupSequence = new LinkedList<>();
// The dependent module is loaded first
makeSequence();
}
@SuppressWarnings("unchecked")
void start( ModuleManager moduleManager) throws ModuleNotFoundException, ServiceNotProvidedException, ModuleStartException {
for (ModuleProvider provider : startupSequence) {
LOGGER.info("start the provider {} in {} module.", provider.name(), provider.getModuleName()); provider.requiredCheck(provider.getModule().services()); provider.start(); }}void notifyAfterCompleted(a) throws ServiceNotProvidedException, ModuleStartException {
for(ModuleProvider provider : startupSequence) { provider.notifyAfterCompleted(); }}private void makeSequence(a) throws CycleDependencyException, ModuleNotFoundException {
List<ModuleProvider> allProviders = new ArrayList<>();
// Check whether all dependent modules exist
for (final ModuleDefine module : loadedModules.values()) {
String[] requiredModules = module.provider().requiredModules();
if(requiredModules ! =null) {
for (String requiredModule : requiredModules) {
if(! loadedModules.containsKey(requiredModule)) {throw new ModuleNotFoundException(requiredModule + " module is required by " + module.provider().getModuleName() + "." + module.provider().name() + ", but not found.");
}
}
}
allProviders.add(module.provider());
}
do {
int numOfToBeSequenced = allProviders.size();
for (int i = 0; i < allProviders.size(); i++) {
ModuleProvider provider = allProviders.get(i);
String[] requiredModules = provider.requiredModules();
if (CollectionUtils.isNotEmpty(requiredModules)) {
// Whether all dependent modules are in startupSequence
boolean isAllRequiredModuleStarted = true;
for (String module : requiredModules) {
boolean exist = false;
for (ModuleProvider moduleProvider : startupSequence) {
if (moduleProvider.getModuleName().equals(module)) {
exist = true;
break; }}if(! exist) { isAllRequiredModuleStarted =false;
break; }}// If all dependent modules are in startupSequence, add objects provided by that module to startupSequence
if(isAllRequiredModuleStarted) { startupSequence.add(provider); allProviders.remove(i); i--; }}else {
// Add the startupSequence if the module provides objects that do not depend on any other modulesstartupSequence.add(provider); allProviders.remove(i); i--; }}// If no object is added to the startupSequence after a loop, then a loop dependency exists
if (numOfToBeSequenced == allProviders.size()) {
StringBuilder unSequencedProviders = new StringBuilder();
allProviders.forEach(provider -> unSequencedProviders.append(provider.getModuleName()).append("[provider=").append(provider.getClass().getName()).append("]\n"));
throw new CycleDependencyException("Exist cycle module dependencies in \n" + unSequencedProviders.substring(0, unSequencedProviders.length() - 1)); }}while(allProviders.size() ! =0); // If the list of supplied objects is not empty, the loop continues}}Copy the code
ApplicationConfiguration
package org.apache.skywalking.oap.server.library.module;
import java.util.HashMap;
import java.util.Properties;
// OAP application configuration class
public class ApplicationConfiguration {
// The module defines the configuration map
private HashMap<String, ModuleConfiguration> modules = new HashMap<>();
// Module configuration list
public String[] moduleList() {
return modules.keySet().toArray(new String[0]);
}
// Add the module definition configuration
public ModuleConfiguration addModule(String moduleName) {
ModuleConfiguration newModule = new ModuleConfiguration();
modules.put(moduleName, newModule);
return newModule;
}
// Check whether the specified module name exists in the module definition configuration map
public boolean has(String moduleName) {
return modules.containsKey(moduleName);
}
// Get the module definition configuration
public ModuleConfiguration getModuleConfiguration(String name) {
return modules.get(name);
}
// The module defines the configuration class
public static class ModuleConfiguration {
// The module provides an object map
private HashMap<String, ProviderConfiguration> providers = new HashMap<>();
private ModuleConfiguration(a) {}// Get the configuration provided by the module
public Properties getProviderConfiguration(String name) {
return providers.get(name).getProperties();
}
// Whether there is a module to provide configuration
public boolean has(String name) {
return providers.containsKey(name);
}
// Add module provides configuration
public ModuleConfiguration addProviderConfiguration(String name, Properties properties) {
ProviderConfiguration newProvider = new ProviderConfiguration(properties);
providers.put(name, newProvider);
return this; }}// The module provides configuration classes
public static class ProviderConfiguration {
// The module provides attributes
private Properties properties;
ProviderConfiguration(Properties properties) {
this.properties = properties;
}
private Properties getProperties(a) {
returnproperties; }}}Copy the code
ApplicationConfigLoader
package org.apache.skywalking.oap.server.starter.config;
import java.io.FileNotFoundException;
import java.io.Reader;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.util.PropertyPlaceholderHelper;
import org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
import org.apache.skywalking.oap.server.library.module.ProviderNotFoundException;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;
import org.apache.skywalking.oap.server.library.util.ResourceUtils;
import org.yaml.snakeyaml.Yaml;
// application.yml loads classes with three layers: module definition name. The module provides a name. property key
@Slf4j
public class ApplicationConfigLoader implements ConfigLoader<ApplicationConfiguration> {
// Use "-" when no module provider is configured
private static final String DISABLE_SELECTOR = "-";
// This field selects the module provider
private static final String SELECTOR = "selector";
private final Yaml yaml = new Yaml();
@Override
public ApplicationConfiguration load(a) throws ConfigFileNotFoundException {
ApplicationConfiguration configuration = new ApplicationConfiguration();
this.loadConfig(configuration);
this.overrideConfigBySystemEnv(configuration);
return configuration;
}
@SuppressWarnings("unchecked")
private void loadConfig(ApplicationConfiguration configuration) throws ConfigFileNotFoundException {
try {
Reader applicationReader = ResourceUtils.read("application.yml");
Map<String, Map<String, Object>> moduleConfig = yaml.loadAs(applicationReader, Map.class);
if (CollectionUtils.isNotEmpty(moduleConfig)) {
selectConfig(moduleConfig);
moduleConfig.forEach((moduleName, providerConfig) -> {
if (providerConfig.size() > 0) {
log.info("Get a module define from application.yml, module name: {}", moduleName);
ApplicationConfiguration.ModuleConfiguration moduleConfiguration = configuration.addModule(moduleName);
providerConfig.forEach((providerName, config) -> {
log.info("Get a provider define belong to {} module, provider name: {}", moduleName, providerName);
finalMap<String, ? > propertiesConfig = (Map<String, ? >) config;final Properties properties = new Properties();
if(propertiesConfig ! =null) {
propertiesConfig.forEach((propertyName, propertyValue) -> {
if (propertyValue instanceof Map) {
Properties subProperties = new Properties();
((Map) propertyValue).forEach((key, value) -> {
subProperties.put(key, value);
replacePropertyAndLog(key, value, subProperties, providerName);
});
properties.put(propertyName, subProperties);
} else{ properties.put(propertyName, propertyValue); replacePropertyAndLog(propertyName, propertyValue, properties, providerName); }}); } moduleConfiguration.addProviderConfiguration(providerName, properties); }); }else {
log.warn("Get a module define from application.yml, but no provider define, use default, module name: {}", moduleName); }}); }}catch (FileNotFoundException e) {
throw newConfigFileNotFoundException(e.getMessage(), e); }}private void replacePropertyAndLog(final Object propertyName, final Object propertyValue, final Properties target, final Object providerName) {
final String valueString = PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(propertyValue + "", target);
if(valueString ! =null) {
if (valueString.trim().length() == 0) {
target.replace(propertyName, valueString);
log.info("Provider={} config={} has been set as an empty string", providerName, propertyName);
} else {
// Use YAML to do data type conversion.
final Object replaceValue = yaml.load(valueString);
if(replaceValue ! =null) {
target.replace(propertyName, replaceValue);
log.info("Provider={} config={} has been set as {}", providerName, propertyName, replaceValue.toString()); }}}}private void overrideConfigBySystemEnv(ApplicationConfiguration configuration) {
for(Map.Entry<Object, Object> prop : System.getProperties().entrySet()) { overrideModuleSettings(configuration, prop.getKey().toString(), prop.getValue().toString()); }}private void selectConfig(final Map<String, Map<String, Object>> moduleConfiguration) {
final Set<String> modulesWithoutProvider = new HashSet<>();
for (final Map.Entry<String, Map<String, Object>> entry : moduleConfiguration.entrySet()) {
final String moduleName = entry.getKey();
final Map<String, Object> providerConfig = entry.getValue();
if(! providerConfig.containsKey(SELECTOR)) {continue;
}
final String selector = (String) providerConfig.get(SELECTOR);
finalString resolvedSelector = PropertyPlaceholderHelper.INSTANCE.replacePlaceholders( selector, System.getProperties() ); providerConfig.entrySet().removeIf(e -> ! resolvedSelector.equals(e.getKey()));if(! providerConfig.isEmpty()) {continue;
}
if(! DISABLE_SELECTOR.equals(resolvedSelector)) {throw new ProviderNotFoundException("no provider found for module " + moduleName + "," + "if you're sure it's not required module and want to remove it, " + "set the selector to -");
}
// now the module can be safely removed
modulesWithoutProvider.add(moduleName);
}
moduleConfiguration.entrySet().removeIf(e -> {
final String module = e.getKey();
final boolean shouldBeRemoved = modulesWithoutProvider.contains(module);
if (shouldBeRemoved) {
log.info("Remove module {} without any provider".module);
}
return shouldBeRemoved;
});
}
private void overrideModuleSettings(ApplicationConfiguration configuration, String key, String value) {
int moduleAndConfigSeparator = key.indexOf('. ');
if (moduleAndConfigSeparator <= 0) {
return;
}
String moduleName = key.substring(0, moduleAndConfigSeparator);
String providerSettingSubKey = key.substring(moduleAndConfigSeparator + 1);
ApplicationConfiguration.ModuleConfiguration moduleConfiguration = configuration.getModuleConfiguration(moduleName);
if (moduleConfiguration == null) {
return;
}
int providerAndConfigSeparator = providerSettingSubKey.indexOf('. ');
if (providerAndConfigSeparator <= 0) {
return;
}
String providerName = providerSettingSubKey.substring(0, providerAndConfigSeparator);
String settingKey = providerSettingSubKey.substring(providerAndConfigSeparator + 1);
if(! moduleConfiguration.has(providerName)) {return;
}
Properties providerSettings = moduleConfiguration.getProviderConfiguration(providerName);
if(! providerSettings.containsKey(settingKey)) {return; } Object originValue = providerSettings.get(settingKey); Class<? > type = originValue.getClass();if (type.equals(int.class) || type.equals(Integer.class))
providerSettings.put(settingKey, Integer.valueOf(value));
else if (type.equals(String.class))
providerSettings.put(settingKey, value);
else if (type.equals(long.class) || type.equals(Long.class))
providerSettings.put(settingKey, Long.valueOf(value));
else if (type.equals(boolean.class) || type.equals(Boolean.class)) {
providerSettings.put(settingKey, Boolean.valueOf(value));
} else {
return;
}
log.info("The setting has been override by key: {}, value: {}, in {} provider of {} module through {}", settingKey, value, providerName, moduleName, "System.properties"); }}Copy the code
ConfigLoader
package org.apache.skywalking.oap.server.starter.config;
// Configure the loading interface
public interface ConfigLoader<T> {
T load(a) throws ConfigFileNotFoundException;
}
Copy the code
OAPServerBootstrap
package org.apache.skywalking.oap.server.starter;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.oap.server.core.RunningMode;
import org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.starter.config.ApplicationConfigLoader;
import org.apache.skywalking.oap.server.starter.config.ConfigLoader;
import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
import org.apache.skywalking.oap.server.telemetry.api.MetricsTag;
// OAP starts the class, loads the configuration file, initializes the module
@Slf4j
public class OAPServerBootstrap {
public static void start(a) {
String mode = System.getProperty("mode");
// Start mode
RunningMode.setMode(mode);
ApplicationConfigLoader configLoader = new ApplicationConfigLoader();
ModuleManager manager = new ModuleManager();
try {
// Load the configuration from the configuration file
ApplicationConfiguration applicationConfiguration = configLoader.load();
// Initialize the module
manager.init(applicationConfiguration);
// Send the boot time to Telemetry
manager.find(TelemetryModule.NAME)
.provider()
.getService(MetricsCreator.class)
.createGauge("uptime"."oap server start up time", MetricsTag.EMPTY_KEY, MetricsTag.EMPTY_VALUE)
// Set uptime to second
.setValue(System.currentTimeMillis() / 1000d);
if (RunningMode.isInitMode()) {
log.info("OAP starts up in init mode successfully, exit now...");
System.exit(0); }}catch (Throwable t) {
log.error(t.getMessage(), t);
System.exit(1); }}}Copy the code
OAPServerStartUp
package org.apache.skywalking.oap.server.starter;
// OAP start class
public class OAPServerStartUp {
public static void main(String[] args) { OAPServerBootstrap.start(); }}Copy the code
In order toSkywalking OAP
Enable the process analysis module loading mechanism
Sequence diagram
Source file: oapServerstartup. SDT
Case: Storage module loading analysis
The configuration file
From the application.yml configuration file, you can see that.
Modules are defined in a three-tier structure:
- The first layer: module definition name
- Layer 2: The module provides the name /
selector
- Layer 3: modules provide configuration information /
selector
The selected module provides the configuration
storage:
selector: ${SW_STORAGE:h2}
elasticsearch:
nameSpace: ${SW_NAMESPACE:""}
clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
# etc...
elasticsearch7:
nameSpace: ${SW_NAMESPACE:""}
clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
# etc...
h2:
driver: ${SW_STORAGE_H2_DRIVER:org.h2.jdbcx.JdbcDataSource}
url: ${SW_STORAGE_H2_URL:jdbc:h2:mem:skywalking-oap-db; DB_CLOSE_DELAY=-1}
# etc...
mysql:
properties:
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://localhost:3306/swtest"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root@1234}
# etc...
tidb:
properties:
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://localhost:4000/tidbswtest"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:""}
# etc...
influxdb:
url: ${SW_STORAGE_INFLUXDB_URL:http://localhost:8086}
user: ${SW_STORAGE_INFLUXDB_USER:root}
password: ${SW_STORAGE_INFLUXDB_PASSWORD:}
# etc...
Copy the code
Load the configuration
After org. Apache. Skywalking. Oap. Server. The starter. Config. ApplicationConfigLoader# load call, . Org. Apache. Skywalking oap. Server. The starter. OAPServerBootstrap# start to get to all need to be loaded modules, including storage module.
In org. Apache. Skywalking) oap) server. The starter. Config. ApplicationConfigLoader# selectConfig through storage. Also the selector = h2, The storage module only retains h2 configuration information:
storage:
h2:
driver: ${SW_STORAGE_H2_DRIVER:org.h2.jdbcx.JdbcDataSource}
url: ${SW_STORAGE_H2_URL:jdbc:h2:mem:skywalking-oap-db; DB_CLOSE_DELAY=-1}
# etc...
Copy the code
Preparation stage
In the org. Apache. Skywalking) oap) server. If the module. The ModuleManager# init, through SPI loading the module definition object, storage module corresponding to the definition of the class is as follows:
PS: You can see that a large number of Service interfaces are defined
package org.apache.skywalking.oap.server.core.storage;
import org.apache.skywalking.oap.server.core.storage.cache.INetworkAddressAliasDAO;
import org.apache.skywalking.oap.server.core.storage.management.UITemplateManagementDAO;
import org.apache.skywalking.oap.server.core.storage.profile.IProfileTaskLogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.profile.IProfileTaskQueryDAO;
import org.apache.skywalking.oap.server.core.storage.profile.IProfileThreadSnapshotQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IAggregationQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IAlarmQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IBrowserLogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ILogQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IMetadataQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.IMetricsQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ITopNRecordsQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ITopologyQueryDAO;
import org.apache.skywalking.oap.server.core.storage.query.ITraceQueryDAO;
import org.apache.skywalking.oap.server.library.module.ModuleDefine;
/** * StorageModule provides the capabilities(services) to interact with the database. With different databases, this * module could have different providers, such as currently, H2, MySQL, ES, TiDB. */
public class StorageModule extends ModuleDefine {
public static final String NAME = "storage";
public StorageModule(a) {
super(NAME);
}
@Override
public Class[] services() {
return newClass[]{ IBatchDAO.class, StorageDAO.class, IHistoryDeleteDAO.class, INetworkAddressAliasDAO.class, ITopologyQueryDAO.class, IMetricsQueryDAO.class, ITraceQueryDAO.class, IMetadataQueryDAO.class, IAggregationQueryDAO.class, IAlarmQueryDAO.class, ITopNRecordsQueryDAO.class, ILogQueryDAO.class, IProfileTaskQueryDAO.class, IProfileTaskLogQueryDAO.class, IProfileThreadSnapshotQueryDAO.class, UITemplateManagementDAO.class, IBrowserLogQueryDAO.class }; }}Copy the code
Also called org. Apache. Skywalking. Oap. Server. If the module. ModuleDefine# prepare into the preparation stage
String[] moduleNames = applicationConfiguration.moduleList();
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())) {
module.prepare(this, applicationConfiguration.getModuleConfiguration(moduleName), moduleProviderLoader);
loadedModules.put(moduleName, module); moduleList.remove(moduleName); }}}Copy the code
In the org. Apache. Skywalking) oap) server. If the module. The ModuleDefine# will prepare by passing in the configuration of the matching in the configuration file only selected modules provide object.
for (ModuleProvider provider : moduleProviderLoader) {
if(! configuration.has(provider.name())) {continue;
}
if (provider.module().equals(getClass())) {
if (loadedProvider == null) {
loadedProvider = provider;
loadedProvider.setManager(moduleManager);
loadedProvider.setModuleDefine(this);
} else {
throw new DuplicateProviderException(this.name() + " module has one " + loadedProvider.name() + "[" + loadedProvider.getClass().getName()
+ "] provider already, " + provider.name() + "[" + provider.getClass().getName() + "] is defined as 2nd provider."); }}}if (loadedProvider == null) {
throw new ProviderNotFoundException(this.name() + " module no provider found.");
}
LOGGER.info("Prepare the {} provider in {} module.", loadedProvider.name(), this.name());
try {
copyProperties(loadedProvider.createConfigBeanIfAbsent(), configuration.getProviderConfiguration(loadedProvider.name()), this.name(), loadedProvider.name());
} catch (IllegalAccessException e) {
throw new ModuleConfigException(this.name() + " module config transport to config bean failure.", e);
}
loadedProvider.prepare();
Copy the code
For example, the “profile” section, select the h2, is loaded with class is org. Apache. Skywalking. Oap. Server storage. The plugin. JDBC. H2. H2StorageProvider
The prepare method is as follows: All Service interfaces declared by the StorageModule are registered
@Override
public void prepare(a) throws ServiceNotProvidedException, ModuleStartException {
Properties settings = new Properties();
settings.setProperty("dataSourceClassName", config.getDriver());
settings.setProperty("dataSource.url", config.getUrl());
settings.setProperty("dataSource.user", config.getUser());
settings.setProperty("dataSource.password", config.getPassword());
h2Client = new JDBCHikariCPClient(settings);
this.registerServiceImplementation(IBatchDAO.class, new H2BatchDAO(h2Client));
this.registerServiceImplementation(
StorageDAO.class,
new H2StorageDAO(
getManager(), h2Client, config.getMaxSizeOfArrayColumn(), config.getNumOfSearchableValuesPerTag())
);
this.registerServiceImplementation(
INetworkAddressAliasDAO.class, new H2NetworkAddressAliasDAO(h2Client));
this.registerServiceImplementation(ITopologyQueryDAO.class, new H2TopologyQueryDAO(h2Client));
this.registerServiceImplementation(IMetricsQueryDAO.class, new H2MetricsQueryDAO(h2Client));
this.registerServiceImplementation(
ITraceQueryDAO.class, new H2TraceQueryDAO(
getManager(),
h2Client,
config.getMaxSizeOfArrayColumn(),
config.getNumOfSearchableValuesPerTag()
));
this.registerServiceImplementation(IBrowserLogQueryDAO.class, new H2BrowserLogQueryDAO(h2Client));
this.registerServiceImplementation(
IMetadataQueryDAO.class, new H2MetadataQueryDAO(h2Client, config.getMetadataQueryMaxSize()));
this.registerServiceImplementation(IAggregationQueryDAO.class, new H2AggregationQueryDAO(h2Client));
this.registerServiceImplementation(IAlarmQueryDAO.class, new H2AlarmQueryDAO(h2Client));
this.registerServiceImplementation(
IHistoryDeleteDAO.class, new H2HistoryDeleteDAO(h2Client));
this.registerServiceImplementation(ITopNRecordsQueryDAO.class, new H2TopNRecordsQueryDAO(h2Client));
this.registerServiceImplementation(
ILogQueryDAO.class,
new H2LogQueryDAO(
h2Client,
getManager(),
config.getMaxSizeOfArrayColumn(),
config.getNumOfSearchableValuesPerTag()
)
);
this.registerServiceImplementation(IProfileTaskQueryDAO.class, new H2ProfileTaskQueryDAO(h2Client));
this.registerServiceImplementation(IProfileTaskLogQueryDAO.class, new H2ProfileTaskLogQueryDAO(h2Client));
this.registerServiceImplementation(
IProfileThreadSnapshotQueryDAO.class, new H2ProfileThreadSnapshotQueryDAO(h2Client));
this.registerServiceImplementation(UITemplateManagementDAO.class, new H2UITemplateManagementDAO(h2Client));
}
Copy the code
Startup phase
In the org. Apache. Skywalking) oap) server. If the module. The ModuleManager# init by calling Org. Apache. Skywalking) oap) server. If the module. The BootstrapFlow# start into the startup phase
void start( ModuleManager moduleManager) throws ModuleNotFoundException, ServiceNotProvidedException, ModuleStartException {
for (ModuleProvider provider : startupSequence) {
LOGGER.info("start the provider {} in {} module.", provider.name(), provider.getModuleName()); provider.requiredCheck(provider.getModule().services()); provider.start(); }}Copy the code
For example, the “profile” section, select the h2, is loaded with class is org. Apache. Skywalking. Oap. Server storage. The plugin. JDBC. H2. H2StorageProvider
Its start method is as follows, as you can see: start H2Client and listen on ModelCreator
@Override
public void start(a) throws ServiceNotProvidedException, ModuleStartException {
final ConfigService configService = getManager().find(CoreModule.NAME)
.provider()
.getService(ConfigService.class);
final int numOfSearchableTracesTags = configService.getSearchableTracesTags().split(Const.COMMA).length;
if (numOfSearchableTracesTags * config.getNumOfSearchableValuesPerTag() > config.getMaxSizeOfArrayColumn()) {
throw new ModuleStartException("Size of searchableTracesTags[" + numOfSearchableTracesTags
+ "] * numOfSearchableValuesPerTag[" + config.getNumOfSearchableValuesPerTag()
+ "] > maxSizeOfArrayColumn[" + config.getMaxSizeOfArrayColumn()
+ "]. Potential out of bound in the runtime.");
}
final int numOfSearchableLogsTags = configService.getSearchableLogsTags().split(Const.COMMA).length;
if (numOfSearchableLogsTags * config.getNumOfSearchableValuesPerTag() > config.getMaxSizeOfArrayColumn()) {
throw new ModuleStartException("Size of searchableLogsTags[" + numOfSearchableLogsTags
+ "] * numOfSearchableValuesPerTag[" + config.getNumOfSearchableValuesPerTag()
+ "] > maxSizeOfArrayColumn[" + config.getMaxSizeOfArrayColumn()
+ "]. Potential out of bound in the runtime.");
}
MetricsCreator metricCreator = getManager().find(TelemetryModule.NAME)
.provider()
.getService(MetricsCreator.class);
HealthCheckMetrics healthChecker = metricCreator.createHealthCheckerGauge(
"storage_h2", MetricsTag.EMPTY_KEY, MetricsTag.EMPTY_VALUE);
h2Client.registerChecker(healthChecker);
try {
h2Client.connect();
H2TableInstaller installer = new H2TableInstaller(
h2Client, getManager(), config.getMaxSizeOfArrayColumn(), config.getNumOfSearchableValuesPerTag());
getManager().find(CoreModule.NAME).provider().getService(ModelCreator.class).addModelListener(installer);
} catch (StorageException e) {
throw newModuleStartException(e.getMessage(), e); }}Copy the code
Notification phase after completion
In the org. Apache. Skywalking) oap) server. If the module. The ModuleManager# init by calling Org. Apache. Skywalking) oap) server. If the module. The BootstrapFlow# notifyAfterCompleted enters the stage after the completion of the notice
void notifyAfterCompleted(a) throws ServiceNotProvidedException, ModuleStartException {
for(ModuleProvider provider : startupSequence) { provider.notifyAfterCompleted(); }}Copy the code
For example, the “profile” section, select the h2, is loaded with class is org. Apache. Skywalking. Oap. Server storage. The plugin. JDBC. H2. H2StorageProvider
Its notifyAfterCompleted method looks like this, and you can see: Nothing to do
@Override
public void notifyAfterCompleted(a) throws ServiceNotProvidedException, ModuleStartException {}Copy the code
H2StorageProvider
Complete source code
package org.apache.skywalking.oap.server.storage.plugin.jdbc.h2;
// etc...
/** * H2 Storage provider is for demonstration and preview only. I will find that haven't implemented several interfaces, * because not necessary, and don't consider about performance very much. * * If someone wants to implement SQL-style database as storage, please just refer the logic. */
@Slf4j
public class H2StorageProvider extends ModuleProvider {
private H2StorageConfig config;
private JDBCHikariCPClient h2Client;
public H2StorageProvider(a) {
config = new H2StorageConfig();
}
@Override
public String name(a) {
return "h2";
}
@Override
public Class<? extends ModuleDefine> module() {
return StorageModule.class;
}
@Override
public ModuleConfig createConfigBeanIfAbsent(a) {
return config;
}
@Override
public void prepare(a) throws ServiceNotProvidedException, ModuleStartException {
Properties settings = new Properties();
settings.setProperty("dataSourceClassName", config.getDriver());
settings.setProperty("dataSource.url", config.getUrl());
settings.setProperty("dataSource.user", config.getUser());
settings.setProperty("dataSource.password", config.getPassword());
h2Client = new JDBCHikariCPClient(settings);
this.registerServiceImplementation(IBatchDAO.class, new H2BatchDAO(h2Client));
this.registerServiceImplementation(
StorageDAO.class,
new H2StorageDAO(
getManager(), h2Client, config.getMaxSizeOfArrayColumn(), config.getNumOfSearchableValuesPerTag())
);
this.registerServiceImplementation(
INetworkAddressAliasDAO.class, new H2NetworkAddressAliasDAO(h2Client));
this.registerServiceImplementation(ITopologyQueryDAO.class, new H2TopologyQueryDAO(h2Client));
this.registerServiceImplementation(IMetricsQueryDAO.class, new H2MetricsQueryDAO(h2Client));
this.registerServiceImplementation(
ITraceQueryDAO.class, new H2TraceQueryDAO(
getManager(),
h2Client,
config.getMaxSizeOfArrayColumn(),
config.getNumOfSearchableValuesPerTag()
));
this.registerServiceImplementation(IBrowserLogQueryDAO.class, new H2BrowserLogQueryDAO(h2Client));
this.registerServiceImplementation(
IMetadataQueryDAO.class, new H2MetadataQueryDAO(h2Client, config.getMetadataQueryMaxSize()));
this.registerServiceImplementation(IAggregationQueryDAO.class, new H2AggregationQueryDAO(h2Client));
this.registerServiceImplementation(IAlarmQueryDAO.class, new H2AlarmQueryDAO(h2Client));
this.registerServiceImplementation(
IHistoryDeleteDAO.class, new H2HistoryDeleteDAO(h2Client));
this.registerServiceImplementation(ITopNRecordsQueryDAO.class, new H2TopNRecordsQueryDAO(h2Client));
this.registerServiceImplementation(
ILogQueryDAO.class,
new H2LogQueryDAO(
h2Client,
getManager(),
config.getMaxSizeOfArrayColumn(),
config.getNumOfSearchableValuesPerTag()
)
);
this.registerServiceImplementation(IProfileTaskQueryDAO.class, new H2ProfileTaskQueryDAO(h2Client));
this.registerServiceImplementation(IProfileTaskLogQueryDAO.class, new H2ProfileTaskLogQueryDAO(h2Client));
this.registerServiceImplementation(
IProfileThreadSnapshotQueryDAO.class, new H2ProfileThreadSnapshotQueryDAO(h2Client));
this.registerServiceImplementation(UITemplateManagementDAO.class, new H2UITemplateManagementDAO(h2Client));
}
@Override
public void start(a) throws ServiceNotProvidedException, ModuleStartException {
final ConfigService configService = getManager().find(CoreModule.NAME)
.provider()
.getService(ConfigService.class);
final int numOfSearchableTracesTags = configService.getSearchableTracesTags().split(Const.COMMA).length;
if (numOfSearchableTracesTags * config.getNumOfSearchableValuesPerTag() > config.getMaxSizeOfArrayColumn()) {
throw new ModuleStartException("Size of searchableTracesTags[" + numOfSearchableTracesTags
+ "] * numOfSearchableValuesPerTag[" + config.getNumOfSearchableValuesPerTag()
+ "] > maxSizeOfArrayColumn[" + config.getMaxSizeOfArrayColumn()
+ "]. Potential out of bound in the runtime.");
}
final int numOfSearchableLogsTags = configService.getSearchableLogsTags().split(Const.COMMA).length;
if (numOfSearchableLogsTags * config.getNumOfSearchableValuesPerTag() > config.getMaxSizeOfArrayColumn()) {
throw new ModuleStartException("Size of searchableLogsTags[" + numOfSearchableLogsTags
+ "] * numOfSearchableValuesPerTag[" + config.getNumOfSearchableValuesPerTag()
+ "] > maxSizeOfArrayColumn[" + config.getMaxSizeOfArrayColumn()
+ "]. Potential out of bound in the runtime.");
}
MetricsCreator metricCreator = getManager().find(TelemetryModule.NAME)
.provider()
.getService(MetricsCreator.class);
HealthCheckMetrics healthChecker = metricCreator.createHealthCheckerGauge(
"storage_h2", MetricsTag.EMPTY_KEY, MetricsTag.EMPTY_VALUE);
h2Client.registerChecker(healthChecker);
try {
h2Client.connect();
H2TableInstaller installer = new H2TableInstaller(
h2Client, getManager(), config.getMaxSizeOfArrayColumn(), config.getNumOfSearchableValuesPerTag());
getManager().find(CoreModule.NAME).provider().getService(ModelCreator.class).addModelListener(installer);
} catch (StorageException e) {
throw newModuleStartException(e.getMessage(), e); }}@Override
public void notifyAfterCompleted(a) throws ServiceNotProvidedException, ModuleStartException {}@Override
public String[] requiredModules() {
return newString[] {CoreModule.NAME}; }}Copy the code
conclusion
The modular mechanism provided by Skywalking is a good design that can be borrowed from multiple N choose 1 scenarios at work.
Reference documentation
- Backend setup
- Configuration Vocabulary
Share and record what you learn and see