专栏首页BAT的乌托邦[享学Eureka] 三、Eureka配置之:EurekaInstanceConfig实例配置

[享学Eureka] 三、Eureka配置之:EurekaInstanceConfig实例配置

目录

  • 前言
  • 正文
    • EurekaInstanceConfig
      • AbstractInstanceConfig
        • PropertiesInstanceConfig 基于配置文件
        • MyDataCenterInstanceConfig
        • CloudInstanceConfig
    • Archaius1Utils
      • 代码示例
    • 代码示例
  • 总结
    • 声明

前言

大家对Spring Cloud技术体系的使用应该有个感受:配置太多了,真的是多如牛毛啊。这是实话且是现状,因此坊间笑言:现在很多架构师为“配置工程师”或许更为恰当。话“粗”理不“粗”,但这足矣体现了配置对一个组件的重要性,so本文及后面几篇文章会着重介绍这些配置,逐个解释其含义,以及辅助代码介绍如何使用。


正文

配置属于基础元数据,它为后面讲解Eureka-Client、Eureka-Server提供基础,同时也是工作中打交道最多点,因此不容忽视。


EurekaInstanceConfig

com.netflix.appinfo.EurekaInstanceConfig是Eureka的应用实例配置接口,它强调的是实例元信息如:实例id、应用名、ip、端口、主机名等等。注意:此处指的实例在client端和server端均是有的,各自取各自所需。

另外,还有个重要配置EurekaClientConfig:它强调的是Client客户端配置,如连接的Server地址、去获取provider的频率、注册自己的频率等

它配置了实例向Eureka服务器注册所需的信息。注册成功后,用户可以基于虚拟主机名(也称为VIPAddress,也叫逻辑名称)去Server端拿到地址列表了。

@ImplementedBy(CloudInstanceConfig.class)
public interface EurekaInstanceConfig {

	String getInstanceId();
	String getAppname();
	String getAppGroupName();
	
	// 初始化后是否开启。若开启了,该实例就可以接受流量了
	// 由于某些实例初始化需要时间,所以通过此属性可以保护实例,默认是false,初始化阶段不接收流量
	boolean isInstanceEnabledOnit();
	
	int getNonSecurePort();
	int getSecurePort();
	boolean isNonSecurePortEnabled();
	boolean getSecurePortEnabled();
	
	// Client多长时间发送一次心跳,告诉自己还活着(默认30s)
	int getLeaseRenewalIntervalInSeconds();
	// Server多久没收到心跳来后,就T除掉该实例
	int getLeaseExpirationDurationInSeconds();

	// 虚拟主机  对应InstanceInfo.vipAddress的值
	String getVirtualHostName();
	// 和上比就是使用的安全端口
	String getSecureVirtualHostName();
	String getASGName();

	// 主机名:默认就是本地的InetAddress.getLocalHost().getHostName()
	// refresh只有在云服务自己配的时候有效。比如AWS上的主机名可能是动态的
	String getHostName(boolean refresh);
	// 元数据
	Map<String, String> getMetadataMap();
	DataCenterInfo getDataCenterInfo();
	// ip地址
	String getIpAddress();
	String getStatusPageUrlPath();
	String getStatusPageUrl();
	String getHomePageUrlPath();
	String getHomePageUrl();
	String getHealthCheckUrlPath();
	String getHealthCheckUrl();
	String getSecureHealthCheckUrl();

	// 获取实例的网络地址,云服务器上会有值
	String[] getDefaultAddressResolutionOrder();
	// 命名空间,默认值是eureka
	String getNamespace();	
}

它的类图如下:

Spring Cloud下新增了EurekaInstanceConfigBean实现,它并不来自于AbstractInstanceConfig而是直接实现了EurekaInstanceConfig接口,用于配置Spring Cloud体系下的组件。

说明:EurekaInstanceConfig在Guice下通过@ImplementedBy来生成实例,所以默认实现是CloudInstanceConfig。而在Spring Cloud下使用的实现类是EurekaInstanceConfigBean~


AbstractInstanceConfig

实现了大部分方法,给与默认的实现。这样用户只需覆盖几个方法就可以注册他们的实例与Server实例配置。

public abstract class AbstractInstanceConfig implements EurekaInstanceConfig {

	// ==========这是一些默认值==========
    private static final int LEASE_EXPIRATION_DURATION_SECONDS = 90;
    private static final int LEASE_RENEWAL_INTERVAL_SECONDS = 30;
    // 关闭Https端口。开启http端口
    private static final boolean SECURE_PORT_ENABLED = false;
    private static final boolean NON_SECURE_PORT_ENABLED = true;
    // 默认端口是80。安全端口443 
    private static final int NON_SECURE_PORT = 80;
    private static final int SECURE_PORT = 443;
    private static final boolean INSTANCE_ENABLED_ON_INIT = false;
    // ip + hostName
    private static final Pair<String, String> hostInfo = getHostInfo();
    // 默认的数据中心
    private DataCenterInfo info = new DataCenterInfo() {
        @Override
        public Name getName() {
            return Name.MyOwn;
        }
    };
    // 获取本地的ip地址和主机名
    private static Pair<String, String> getHostInfo() {
        Pair<String, String> pair;
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            pair = new Pair<String, String>(localHost.getHostAddress(), localHost.getHostName());
        } catch (UnknownHostException e) {
            logger.error("Cannot get host info", e);
            pair = new Pair<String, String>("", "");
        }
        return pair;
    }


    @Override
    public String getVirtualHostName() {
        return (getHostName(false) + ":" + getNonSecurePort());
    }
    @Override
    public String getIpAddress() {
        return hostInfo.first();
    }
    @Override
    public String getHostName(boolean refresh) {
        return hostInfo.second();
    }
    // 空实现。毕竟元数据不是必须的,子类有需要就自己实现呗~~~~~~
    // `PropertiesInstanceConfig`实现了该方法
    @Override
    public Map<String, String> getMetadataMap() {
        return null;
    }

}

这一波默认值操作,已经覆盖了大部分的方法,留给子类的已经不多了。


PropertiesInstanceConfig 基于配置文件

基于配置文件Eureka应用实例配置抽象类,也是其他具体实现的基类。这里指的配置指的是Archaius管理的配置。

public abstract class PropertiesInstanceConfig extends AbstractInstanceConfig {

	protected final String namespace;
	protected final DynamicPropertyFactory configInstance;
	private String appGrpNameFromEnv;
}
  • namespace:命名空间。默认值是eureka
  • configInstance:不解释,动态属性工厂。
    • 初始化它时调用了Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME)从而完成全局属性的加载,具体参考问下的详细讲解
  • appGrpNameFromEnv:应用分组名称。通过配置文件中NETFLIX_APP_GROUP来配置值,默认值unknown
    • 当然,最终的getAppGroupName()取值自:eureka.appGroup,若没配置才取值appGrpNameFromEnv

其他接口方法的实现也均基于从配置里去读取(没读到就取父类的默认值):

PropertiesInstanceConfig:

    @Override
    public boolean isInstanceEnabledOnit() {
        return configInstance.getBooleanProperty(namespace + TRAFFIC_ENABLED_ON_INIT_KEY, super.isInstanceEnabledOnit()).get();
    }
    @Override
    public int getNonSecurePort() {
        return configInstance.getIntProperty(namespace + PORT_KEY, super.getNonSecurePort()).get();
    }
    // eureka.instanceId = xxx
    @Override
    public String getInstanceId() {
        String result = configInstance.getStringProperty(namespace + INSTANCE_ID_KEY, null).get();
        return result == null ? null : result.trim();
    }
    ...

	// 默认的path是:/Status
	// 默认是homePage是:/
	// 默认的healthCheck是:/healthcheck
	// 生产环境中,这三者都需要改~~~~~~~~~~~~~
    @Override
    public String getStatusPageUrlPath() {
        return configInstance.getStringProperty(namespace + STATUS_PAGE_URL_PATH_KEY, Values.DEFAULT_STATUSPAGE_URLPATH).get();
    }

最特殊的当属它对metadata元数据的处理:

    @Override
    public Map<String, String> getMetadataMap() {
        String metadataNamespace = namespace + INSTANCE_METADATA_PREFIX + ".";
        Map<String, String> metadataMap = new LinkedHashMap<String, String>();
        Configuration config = (Configuration) configInstance.getBackingConfigurationSource();
        String subsetPrefix = metadataNamespace.charAt(metadataNamespace.length() - 1) == '.'
                ? metadataNamespace.substring(0, metadataNamespace.length() - 1)
                : metadataNamespace;
        for (Iterator<String> iter = config.subset(subsetPrefix).getKeys(); iter.hasNext(); ) {
            String key = iter.next();
            String value = config.getString(subsetPrefix + "." + key);
            metadataMap.put(key, value);
        }
        return metadataMap;
    }

简单的说元数据你可以这么来配置:eureka.metadata.aaa=aaaValue eureka.metadata.bbb=bbbValue...

该类是在父类默认值基础上的提升:先从Archaius管理的配置文件中查找值,找不到才是父类默认值。因为扩展到了外部化配置,所以弹性更大且可以配置metadata元数据了。

它用有两个子类:MyDataCenterInstanceConfigCloudInstanceConfig,他俩作为该接口唯二的两个真正实现类。


MyDataCenterInstanceConfig

它没有任何自己的方法实现,就是确定了namespace的值使用全局的,也就是默认是eureka


CloudInstanceConfig

用于AWS云部署的配置。

// RefreshableInstanceConfig接口仅有一个方法:String resolveDefaultAddress(boolean refresh);
@Singleton
@ProvidedBy(CloudInstanceConfigProvider.class)
public class CloudInstanceConfig extends PropertiesInstanceConfig implements RefreshableInstanceConfig {

    private static final String[] DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER = new String[] {MetaDataKey.publicHostname.name(), MetaDataKey.localIpv4.name()};
	
	// 提供一个AmazonInfo实例
	// 构造期间会初始化一个`Archaius1AmazonInfoConfig`,这样它会加载默认配置们。参考Archaius1Utils的逻辑
	private final RefreshableAmazonInfoProvider amazonInfoHolder;
    public CloudInstanceConfig() {
        this(CommonConstants.DEFAULT_CONFIG_NAMESPACE);
    }
    public CloudInstanceConfig(String namespace) {
        this(namespace, new Archaius1AmazonInfoConfig(namespace), null, true);
    }

}

它主要从写了几个可以从Cloud元数据里获取值的方法:

CloudInstanceConfig:

	// 注意:是publicHostname
    @Override
    public String getHostName(boolean refresh) {
        if (refresh) {
            amazonInfoHolder.refresh();
        }
        return amazonInfoHolder.get().get(MetaDataKey.publicHostname);
    }
    // 但地址为毛不是publicIpv4呢???
    @Override
    public String getIpAddress() {
        String ipAddr = amazonInfoHolder.get().get(MetaDataKey.localIpv4);
        return ipAddr == null ? super.getIpAddress() : ipAddr;
    }
	...

还记得EurekaInstanceConfig接口头上标注的@ImplementedBy(CloudInstanceConfig.class)注解麽?它就是该接口依赖注入的默认实现(当然前提是你使用Guice完成依赖管理)。


Archaius1Utils

它是针对Archaius1.x的一个工具类,用于读取配置。

public final class Archaius1Utils {

    private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
    private static final String EUREKA_ENVIRONMENT = "eureka.environment";

	// 初始化配置。传入的`configName`这个仅仅只是一个默认值而已,外部化配置优先
    public static DynamicPropertyFactory initConfig(String configName) {
        DynamicPropertyFactory configInstance = DynamicPropertyFactory.getInstance();
        DynamicStringProperty EUREKA_PROPS_FILE = configInstance.getStringProperty("eureka.client.props", configName);

        String env = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT, "test");
        ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);

        String eurekaPropsFile = EUREKA_PROPS_FILE.get();
        try {
            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);

        }
        return configInstance;
    }

}

初始化步骤如下:

  1. 得到eureka的配置文件名/所在地
    1. 若全局配置中读取到你配置了eureka.client.props,那就以配置的这个为主
    2. 若木有配置此key,那就使用传入的configName文件名
  2. 从全局配置中读取到eureka.environment这个key(若没配置默认值是test),然后把得到的值设置到全局属性中:archaius.deployment.environment = 得到的值,这就是部署上下文了
  3. 这么依赖,全局配置里已经有了部署环境值,因此在调用ConfigurationManager.loadCascadedPropertiesFromResources(eurekaPropsFile);就会去加载如下配置文件:
    1. 加载configName.properties文件
    2. 加载configName-环境名.properties文件(值覆盖前者)

需要注意的是:一般eureka.client.props这个我们并不需要配置,使用方法传入的configName的值,它的默认值是:eureka-client。也就说默认情况下,当你类路径下有如下文件:

  • eureka-client.properties
  • eureka-client-环境名(默认是test).properties

均会被自动加载到全局配置里。因此:强联建议你关于eureka-client/instance的配置你都放在单独文件里,分开管理真的更好,不要什么都只知道一个application.properties文件,分而治之是提效、避免出错的一个有效手段。


代码示例

主配置config.properties

eureka.client.props = yourbatman
eureka.environment = prod
xxx.name = YourBatman

次配置yourbatman.properties

xxx.age=18

环境相关配置yourbatman-prod.properties

xxx.age=100

书写测试程序:

@Test
public void fun3(){
    DynamicPropertyFactory factory = Archaius1Utils.initConfig(null);

    System.out.println(factory.getStringProperty("xxx.name","").get());
    System.out.println(factory.getStringProperty("xxx.age","").get());
}

运行程序,控制台打印:

YourBatman
100

该工具方法调用多次和一次的效果一样~


代码示例

主配置类config.properties

## euraka配置
# 对应AmazonInfoConfig
eureka.validateInstanceId = false

eureka.traffic.enabled = false
eureka.securePort.enabled = false
eureka.instanceId = xxx-1

书写测试程序:

@Test
public void fun4(){
    // 使用它就得准备一个AmazonInfo配置AmazonInfoConfig 默认使用的Archaius1AmazonInfoConfig
    // 因此也是直接放在配置文件里即可
    CloudInstanceConfig instanceConfig = new CloudInstanceConfig();

    System.out.println(instanceConfig.getInstanceId());
    System.out.println(instanceConfig.isInstanceEnabledOnit());
    System.out.println(instanceConfig.getSecurePortEnabled());
}

运行,控制台输出:

22:01:41.205 [main] INFO com.netflix.appinfo.RefreshableAmazonInfoProvider - Datacenter is: Amazon
xxx-1
false
false

总结

关于Eureka配置之:EurekaInstanceConfig实例配置就先介绍到这。相信通过这篇文章你已经知道如何给Eureka来提供配置了,当然喽这种配置方式是源生Eureka的方式,若在Spring Cloud下,方式可不一样。具体在讲到和Spring Cloud集成的使用的时候会再介绍~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解

    上篇文章介绍了Archaius动态属性DynamicProperty,并且通过DynamicPropertyFactory间接的体验了一把它天生的动态性。

    YourBatman
  • 【小家Spring】Spring Framework提供的实用纯Java工具类大合集(一)

    在Spring Framework里的spring-core核心包里面,有个org.springframework.util里面有不少非常实用的工具类。

    YourBatman
  • [享学Netflix] 十四、Archaius如何对多环境、多区域、多云部署提供配置支持?

    在当下日益复杂的互联网云环境中,对应用APP的灵活部署要求越来越高:同样的一份代码在不同环境、不同区域…需要有不同表现(逻辑不同、性能不同…),同时高可用方面的...

    YourBatman
  • Spark Tips3: 在Spark Streaming job中读取Kafka messages及其offsetRange

    在Spark Streaming job中读取Kafka topic(s)中的messages时,有时我们会需要同步记录下每次读取的messages的offse...

    叶锦鲤
  • JMail接收发送邮件使用参考

    用户2135432
  • 大数据算法设计模式(2) - 左外链接(leftOuterJoin) spark实现

    左外链接(leftOuterJoin) spark实现 package com.kangaroo.studio.algorithms.join; impor...

    用户1225216
  • 这样规范写代码,同事直呼“666”

    zhisheng
  • 厉害了,关于String的10道经典面试题。

    1、String是基本数据类型吗? 2、String是可变的话? 3、怎么比较两个字符串的值一样,怎么比较两个字符串是否同一对象? 4、switch中可以使用S...

    Java技术栈
  • String性能提升10倍的几个方法!(源码+原理分析)

    String 类型是我们使用最频繁的数据类型,没有之一。那么提高 String 的运行效率,无疑是提升程序性能的最佳手段。

    Java中文社群_老王
  • 如何更规范化编写 Java 代码

    如何更规范化编写 Java 代码的重要性想必毋需多言,其中最重要的几点当属提高代码性能、使代码远离 Bug、令代码更优雅。

    淡定的蜗牛

扫码关注云+社区

领取腾讯云代金券