[享学Eureka] 三、Eureka配置之:EurekaInstanceConfig实例配置
- 2020 年 3 月 30 日
- 筆記
目录
- 前言
- 正文
- EurekaInstanceConfig
- AbstractInstanceConfig
- PropertiesInstanceConfig 基于配置文件
- MyDataCenterInstanceConfig
- CloudInstanceConfig
- AbstractInstanceConfig
- Archaius1Utils
- 代码示例
- 代码示例
- EurekaInstanceConfig
- 总结
- 声明
前言
大家对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元数据了。
它用有两个子类:MyDataCenterInstanceConfig
和CloudInstanceConfig
,他俩作为该接口唯二的两个真正实现类。
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; } }
初始化步骤如下:
- 得到eureka的配置文件名/所在地
- 若全局配置中读取到你配置了
eureka.client.props
,那就以配置的这个为主 - 若木有配置此key,那就使用传入的configName文件名
- 若全局配置中读取到你配置了
- 从全局配置中读取到
eureka.environment
这个key(若没配置默认值是test),然后把得到的值设置到全局属性中:archaius.deployment.environment = 得到的值
,这就是部署上下文了 - 这么依赖,全局配置里已经有了部署环境值,因此在调用
ConfigurationManager.loadCascadedPropertiesFromResources(eurekaPropsFile);
就会去加载如下配置文件:- 加载
configName.properties
文件 - 加载
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集成的使用的时候会再介绍~