上篇文章已经介绍了 为何要读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); } } }
看下注释 我们可以了解到几个关键点:
接着近一步往下跟,这里可以先看 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"); } }
这里注释写的比较详细,且这里有两个重点:
这里一个关键点 是使用了很经典的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修饰来保证线程之间的可见性,用synchronized来保证线程串行化,double check来保证不被单例化。
接着我们就继续往下跟,看看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具体的细节这里也有两篇比较好的文章推荐:
先看代码:
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 和公众号: 壹枝花算不算浪漫 ,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫