【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取
前言
上篇文章已经介绍了 为何要读netflix eureka源码了,这里就不再概述,下面开始正式源码解读的内容。
如若转载 请标明来源:一枝花算不算浪漫
代码总览
还记得上文中,我们通过web.xml找到了eureka server入口的类EurekaBootStrap
,这里我们就先来简单地看下:
/**
* The class that kick starts the eureka server. 负责启动Eureka server的类
*
* <p>
* 这里要注意两个关键点:
* eureka server对应的配置类为:EurekaServerConfig
* eureka client对应的配置类为:EurekaInstanceConfig
*
* The eureka server is configured by using the configuration
* {@link EurekaServerConfig} specified by <em>eureka.server.props</em> in the
* classpath. The eureka client component is also initialized by using the
* configuration {@link EurekaInstanceConfig} specified by
* <em>eureka.client.props</em>. If the server runs in the AWS cloud, the eureka
* server binds it to the elastic ip as specified.
* </p>
*
* @author Karthik Ranganathan, Greg Kim, David Liu
*
* 负责EurekaServer初始化的类
*/
public class EurekaBootStrap implements ServletContextListener {
/**
* Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.
*
* @see
* javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent event) {
try {
initEurekaEnvironment();
initEurekaServerContext();
ServletContext sc = event.getServletContext();
sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
} catch (Throwable e) {
logger.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
}
看下注释 我们可以了解到几个关键点:
- eureka server对应的配置类为:EurekaServerConfig
- eureka client对应的配置类为:EurekaInstanceConfig
- EurekaBootStrap implements ServletContextListener, 所以这里会直接执行contextInitialized方法。
Eureka-Server 环境配置
初始化enviroment
接着近一步往下跟,这里可以先看 initEurekaEnvironment()
代码如下:
protected void initEurekaEnvironment() throws Exception {
logger.info("Setting the eureka configuration..");
// 获取dataCenter数据中心 这里重点看ConfigurationManager
// ConfigurationManager:配置管理器,管理eureka自己所有的配置,
// 重点:getConfigInstance里面使用的是volatile+synchronized+double check模式的单例模式
/**
* ConfigurationManager 创建过程:(继续往后跟读代码)
* 1、创建一个ConcurrentCompositeConfiguration实例,这个类代表了所谓的配置,包括eureka需要的所有配置。
* 2、往ConcurrentCompositeConfiguration加入一堆config,然后返回ConfigurationManager实例
* 3、初始化数据中心的配置,如果没有配置的话就是default data center
* 4、初始化eureka 运行的环境,如果没有配置的话,默认就是test环境
*/
String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
// 初始化数据中心,没有配置的话 使用DEFAULT data center
if (dataCenter == null) {
logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
} else {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
}
// 获取eureka server运行环境,没有配置的话默认使用test环境
// 后面读取配置文件会根据运行环境读取,比如eureka-server-test.properties
String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
if (environment == null) {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
}
}
这里注释写的比较详细,且这里有两个重点:
- getConfigInstance里面使用的是volatile+synchronized+double check模式的单例模式
- ConfigurationManager 创建过程
getConfigInstance
这里一个关键点 是使用了很经典的double check 单例模式。
这种单例是一种线程安全的方式,里面使用了volatile+synchronized+double check,具体秒在何处 我这里就不展开讲解了,搜索double check单例模式就会有很多解析文章,这里直接看代码。
static volatile AbstractConfiguration instance = null;
/**
* Get the current system wide configuration. If there has not been set, it will return a default
* {@link ConcurrentCompositeConfiguration} which contains a SystemConfiguration from Apache Commons
* Configuration and a {@link DynamicURLConfiguration}.
*/
public static AbstractConfiguration getConfigInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
}
}
}
return instance;
}
这里instance用volatile修饰来保证线程之间的可见性和有序性(禁止指令重排序),一般的对象创建过程都是非原子性的,内部会发生指令重排序的情况,所以加上volatile可以防止指令重排。用synchronized来保证线程串行化,double check来保证不被单例化。
接着我们就继续往下跟,看看ConfigurationManager的创建过程。
ConfigurationManager 创建
private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
if (instance == null && !defaultConfigDisabled) {
instance = createDefaultConfigInstance();
registerConfigBean();
}
return instance;
}
private static AbstractConfiguration createDefaultConfigInstance() {
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
try {
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
} catch (Throwable e) {
logger.warn("Failed to create default dynamic configuration", e);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
config.addConfiguration(envConfig, ENV_CONFIG_NAME);
}
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));
return config;
}
public ConcurrentCompositeConfiguration()
{
clear();
}
public final void clear()
{
fireEvent(EVENT_CLEAR, null, null, true);
configList.clear();
namedConfigurations.clear();
// recreate the in memory configuration
containerConfiguration = new ConcurrentMapConfiguration();
containerConfiguration.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
containerConfiguration.setListDelimiter(getListDelimiter());
containerConfiguration.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
containerConfiguration.addConfigurationListener(eventPropagater);
configList.add(containerConfiguration);
overrideProperties = new ConcurrentMapConfiguration();
overrideProperties.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
overrideProperties.setListDelimiter(getListDelimiter());
overrideProperties.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
overrideProperties.addConfigurationListener(eventPropagater);
fireEvent(EVENT_CLEAR, null, null, false);
containerConfigurationChanged = false;
invalidate();
}
上面代码是比较多,如果一行行去抠细节 真的就没有必要了,这里我们只是看一些重点的流程,我们上面注释也写到过ConfigurationManager的创建过程:
1、创建一个ConcurrentCompositeConfiguration实例,这个类代表了所谓的配置,包括eureka需要的所有配置。
2、往ConcurrentCompositeConfiguration加入一堆config,然后返回ConfigurationManager实例
这里我是不建议太过于扣细节的,因为往往这些细枝末节的东西会将我们绕进去。
关于ConfigurationManager具体的细节这里也有两篇比较好的文章推荐:
Eureka-Server 上下文加载
先看代码:
protected void initEurekaServerContext() throws Exception {
// 1、加载eureka-server properties文件中和配置
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
logger.info("Initializing the eureka client...");
logger.info(eurekaServerConfig.getJsonCodecName());
ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);
// 2、初始化一个ApplicationInfoManager,和第3步创建eureka client相关,后续会讲解
ApplicationInfoManager applicationInfoManager = null;
// 3、初始化eureka-server内部的一个eureka-client(用来跟其他的eureka-server节点做注册和通信)
// 类的开头已经说明了:EurekaInstanceConfig其实就是eureka client相关的配置类
if (eurekaClient == null) {
EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
? new CloudInstanceConfig()
: new MyDataCenterInstanceConfig();
applicationInfoManager = new ApplicationInfoManager(
instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
} else {
applicationInfoManager = eurekaClient.getApplicationInfoManager();
}
// 3、处理注册相关的事情
PeerAwareInstanceRegistry registry;
if (isAws(applicationInfoManager.getInfo())) {
registry = new AwsInstanceRegistry(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
awsBinder.start();
} else {
registry = new PeerAwareInstanceRegistryImpl(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
}
// 4、处理peer节点相关的事情
PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
registry,
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
applicationInfoManager
);
// 5、完成eureka-server上下文(context)的构建及初始化
serverContext = new DefaultEurekaServerContext(
eurekaServerConfig,
serverCodecs,
registry,
peerEurekaNodes,
applicationInfoManager
);
EurekaServerContextHolder.initialize(serverContext);
serverContext.initialize();
logger.info("Initialized server context");
// Copy registry from neighboring eureka node
// 6、处理一些善后的事情,从相邻的eureka节点拷贝注册信息
int registryCount = registry.syncUp();
registry.openForTraffic(applicationInfoManager, registryCount);
// Register all monitoring statistics.
// 7、注册所有的监控统计项
EurekaMonitors.registerAllStats();
}
代码有点长,加载context信息分为了上面注释的好几步,代码注释都有写
加载eureka-server properties文件中和配置
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
private static final DynamicStringProperty EUREKA_PROPS_FILE = DynamicPropertyFactory.getInstance().getStringProperty("eureka.server.props","eureka-server");
public DefaultEurekaServerConfig() {
init();
}
private void init() {
String env = ConfigurationManager.getConfigInstance().getString(
EUREKA_ENVIRONMENT, TEST);
ConfigurationManager.getConfigInstance().setProperty(
ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);
String eurekaPropsFile = EUREKA_PROPS_FILE.get();
try {
// ConfigurationManager
// .loadPropertiesFromResources(eurekaPropsFile);
ConfigurationManager
.loadCascadedPropertiesFromResources(eurekaPropsFile);
} catch (IOException e) {
logger.warn(
"Cannot find the properties specified : {}. This may be okay if there are other environment "
+ "specific properties or the configuration is installed with a different mechanism.",
eurekaPropsFile);
}
}
public static void loadCascadedPropertiesFromResources(String configName) throws IOException {
Properties props = loadCascadedProperties(configName);
if (instance instanceof AggregatedConfiguration) {
ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
config.loadProperties(props);
((AggregatedConfiguration) instance).addConfiguration(config, configName);
} else {
ConfigurationUtils.loadProperties(props, instance);
}
}
首先我们看下EurekaServerConfig
:
里面包含好多getxxx方法,看一下具体实现:
其中configInstance是DynamicPropertyFactory
对象。EurekaServerConfig,这是个接口,这里面有一堆getXXX()的方法,包含了eureka server需要使用的所有的配置,都可以通过这个接口来获取。
想象一下,eureka-sever.properties文件里,都是一个一个的key=value的很多的配置项,肯定是将这些key-value格式的配置项加载到内存的Properties对象去存放,Map。一般来说,如果让我们自己来设计这个读取properties文件的配置的代码,也许我们就是做到将配置加载到Properties对象中就结束了。
EurekaServerConfig,代表了eureka-server需要的所有的配置项,通过接口定义了大量的方法,让你可以从这里获取所有你需要的配置
DefaultEurekaServerConfig
就是上面EurekaServerConfig
的实现类,创建实例的时候,会执行一个init()方法,在这个方法中,就会完成eureka-server.properties文件中的配置项的加载。EUREKA_PROPS_FILE,对应着要加载的eureka的配置文件的名字。
将加载出来的Properties中的配置项都放到ConfigurationManager中去,由这个ConfigurationManager来管理
比如说eureka-server那个工程里,就有一个src/main/resources/eureka-server.properties文件,只不过里面是空的,全部都用了默认的配置
DefaultEurekaServerConfig.init()方法中,会将eureka-server.properties文件中的配置加载出来,都放到ConfdigurationManager中去,然后在DefaultEurekaServerConfig的各种获取配置项的方法中,配置项的名字是在各个方硬编码的,是从一个DynamicPropertyFactory里面去获取的,你可以认为DynamicPropertyFactory是从ConfigurationManager那儿来的,因为ConfigurationManager中都包含了加载出来的配置了,所以DynamicPropertyFactory里,也可以获取到所有的配置项
在从DynamicPropertyFactory中获取配置项的时候,如果你没配置,那么就用默认值,全部都给你弄好了各个配置项的默认值,相当于所有的配置项的默认值,在DefaultEurekaServerConfig的各个方法中,都可以看到,如果你没配置,那么就用这里的默认值就可以了
加载eureka-server.properties的过程:
(1)创建了一个DefaultEurekaServerConfig对象
(2)创建DefaultEurekaServerConfig对象的时候,在里面会有一个init方法
(3)先是将eureka-server.properties中的配置加载到了一个Properties对象中,然后将Properties对象中的配置放到ConfigurationManager中去,此时ConfigurationManager中去就有了所有的配置了
(4)然后DefaultEurekaServerConfig提供的获取配置项的各个方法,都是通过硬编码的配置项名称,从DynamicPropertyFactory中获取配置项的值,DynamicPropertyFactory是从ConfigurationManager那儿来的,所以也包含了所有配置项的值
(5)在获取配置项的时候,如果没有配置,那么就会有默认的值,全部属性都是有默认值的
申明
本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫