程序有问题时不要担心,如果所有东西都没问题,你就失业了。
–> 返回Netflix OSS套件专栏汇总 <–
代码下载地址:https://github.com/f641385712/netflix-learning
前言
如果说Eureka里最核心的一个对象/类是什么,我想当属InstanceInfo
了。它贯穿于Client、Server
俩端,承载着一个实例的所有描述,它是事件的主体,一切皆围绕着它来进行。
另外,还需注意的是Eureka使用的是Guice作为它的依赖注入DI基础组件,因此源码处你进场能看见@Singleton、@Inject
等注解的使用,为了不妨碍你的阅读和研究,建议可先认识下Google Guice
这个轻量级依赖注入的相关支持,这里我也很暖心的给你准备好了直达电梯:3分钟带你了解轻量级依赖注入框架Google Guice【享学Java】。
正文
本文会逐个介绍InstanceInfo
以及LeaseInfo
的属性,最后辅以一个示例讲解。可以作为字典、参考文章来使用,建议搜藏or转发。
InstanceInfo 实例信息
InstanceInfo
代表一个实例的信息,是一个趋近于POJO
的类,因此理解起来并不难。但由于字段众多(30+个),因此先围绕它理解一圈,基本能窥探到注册中心的全貌,所以还是蛮重要的。
成员属性
// @ProvidedBy是Guice的注解,用于在Guice的DI依赖注入时生成一个InstanceInfo实例
@ProvidedBy(EurekaConfigBasedInstanceInfoProvider.class)
// 序列化/反序列化化使用定制的序列化器
@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
// 支持xml的序列化/反序列化 它俩都用instance前缀包裹着。形如{"instance" : {...}} 才行
@XStreamAlias("instance")
// 支持json的序列化/反序列化
@JsonRootName("instance")
public class InstanceInfo {
private volatile String instanceId;
private volatile String appName;
@Auto
private volatile String appGroupName;
private volatile String ipAddr;
@Deprecated
private volatile String sid = SID_DEFAULT;
private volatile int port = DEFAULT_PORT;
private volatile int securePort = DEFAULT_SECURE_PORT;
@Auto
private volatile String homePageUrl;
@Auto
private volatile String statusPageUrl;
@Auto
private volatile String healthCheckUrl;
@Auto
private volatile String secureHealthCheckUrl;
@Auto
private volatile String vipAddress;
@Auto
private volatile String secureVipAddress;
@XStreamOmitField
private String statusPageRelativeUrl;
@XStreamOmitField
private String statusPageExplicitUrl;
@XStreamOmitField
private String healthCheckRelativeUrl;
@XStreamOmitField
private String healthCheckSecureExplicitUrl;
@XStreamOmitField
private String vipAddressUnresolved;
@XStreamOmitField
private String secureVipAddressUnresolved;
@XStreamOmitField
private String healthCheckExplicitUrl;
@Deprecated
private volatile int countryId = DEFAULT_COUNTRY_ID; // Defaults to US
private volatile boolean isSecurePortEnabled = false;
private volatile boolean isUnsecurePortEnabled = true;
private volatile DataCenterInfo dataCenterInfo;
private volatile String hostName;
private volatile InstanceStatus status = InstanceStatus.UP;
private volatile InstanceStatus overriddenStatus = InstanceStatus.UNKNOWN;
@XStreamOmitField
private volatile boolean isInstanceInfoDirty = false;
private volatile LeaseInfo leaseInfo;
@Auto
private volatile Boolean isCoordinatingDiscoveryServer = Boolean.FALSE;
@XStreamAlias("metadata")
private volatile Map<String, String> metadata;
@Auto
private volatile Long lastUpdatedTimestamp;
@Auto
private volatile Long lastDirtyTimestamp;
@Auto
private volatile ActionType actionType;
@Auto
private volatile String asgName;
private String version = VERSION_UNKNOWN;
}
需要强调一点:在Guice下注入该实例时由EurekaConfigBasedInstanceInfoProvider
负责创建;但是在Spring Cloud
下该实例由自己提供的InstanceInfoFactory
完成创建的。
Spring Cloud下完全没有使用
Guice
来管理依赖,而是自己实现的管理,毕竟它也支持@Inject
等标准注解嘛,接手过来比较容易
instanceId
:实例id。在同一个应用appName的范围内是必须是惟一的- 你常见的在Spring Cloud的配置是:
eureka.instance.instance-id = ${spring.cloud.client.ipAddress}:${spring.application.name}:${server.port}:@project.version@
- 在eureka项目本身的配置是:
eureka.instanceId = xxxxxxx
,请注意区别 - 此处
project.version
是引用maven里面的属性,因为Spring Boot的parent包将maven中默认的${*}
修改成了@*@
,所以引用maven属性要用@@
- 在eureka项目本身的配置是:
- 你常见的在Spring Cloud的配置是:
appName
:应用名。如ACCOUNT
(同一应用可以有N多个实例)- 此属性最终会被序列化
app
这个key,如app=ACCOUNT
- 此属性最终会被序列化
appGroupName
:应用组名。多个应用可分组,很少用,一般为nullipAddr
:本实例的ip地址。如ipAddr=192.168.1.100
sid
:已过期属性。不用搭理port
:端口号。默认值是7001securePort
:安全端口号。默认值是7002homePageUrl
:主页。如homePageUrl=http://localhost:8080
- 一般使用占位符形式配置
xxx.homePageUrl = http://${mynamespace.hostname}:7001
- 这个占位符在运行期会被用hostname属性替换掉,而hostname属于必配的属性
- 一般使用占位符形式配置
statusPageUrl
:状态页。如http://localhost:8080/actuator/info
- 同上,一般也使用占位符形式
healthCheckUrl
:健康检查的URL(Rest)。如http://localhost:8080/actuator/health
secureHealthCheckUrl
:一般不用,为null即可。vipAddress
:逻辑地址。如vipAddress=ACCOUNT
- 关于它在eureka中如何使用,会有详解
secureVipAddress
:略statusPageRelativeUrl
:相对URL。最终会拼接全了给statusPageUrl
赋值,如你/api/v1/status
最终给statusPageUrl
赋值为(举个例子):http://localhost:8080//api/v1/status
statusPageExplicitUrl
:明确的URL。最终处理后给statusPageUrl
赋值。如你配置了${mynamespace.hostname}/api/v1/status
,那么最终占位符部分会被替换为hostname的值,从而效果同上healthCheckRelativeUrl
:略healthCheckSecureExplicitUrl
:略vipAddressUnresolved
:略secureVipAddressUnresolved
:略healthCheckExplicitUrl
:略countryId
:过期。国家ID,默认值是1表示美国,不用搭理isSecurePortEnabled
:是否启用安全端口,securePort
有值就是true,显然默认是falseisUnsecurePortEnabled
:默认是truedataCenterInfo
:数据中心。- 关于数据中心的概念,后文也会有详细分析其使用
hostName
:必须的。主机名,如hostName=LP-BJ4556.baidu.work
- 说明:Spring Cloud下的服务注册实例id默认使用主机名,而非ip地址,毕竟
http://hostname:port
这种方式也是可以访问的接口的(当然这个可配)
- 说明:Spring Cloud下的服务注册实例id默认使用主机名,而非ip地址,毕竟
status
:实例状态。默认值是InstanceStatus.UP
,是个枚举starting
:实例初始化状态,此状态主要给实例预留初始化时间down
:当健康检查失败时,实例的状态转变到downup
:正常服务状态out_of_service
:不参与接收服务 。但是服务正常unknown
:未知状态
overriddenStatus
:eureka解决状态覆盖而存在的属性字段。它的默认是是InstanceStatus.UNKNOWN
- 状态覆盖也是ereuka里的一个小亮点,后有详细介绍
isInstanceInfoDirty
:标注实例数据是否是脏的(client和server端对比)- true:表示 InstanceInfo 在 Eureka-Client 和 Eureka-Server 数据不一致,需要注册。
- 每次 InstanceInfo 发生属性变化时,以及InstanceInfo 刚被创建时,会标记此值是true
- 当符合条件时,InstanceInfo 不会立即向 Eureka-Server 注册,而是后台线程定时注册(当然若开启了
eureka.shouldOnDemandUpdateStatusChange = true
时是立即注册)- 该配置属于
EurekaClientConfig
端配置哦,不属于Instance配置
- 该配置属于
- false:表示一致,不需要做额外动作
- true:表示 InstanceInfo 在 Eureka-Client 和 Eureka-Server 数据不一致,需要注册。
leaseInfo
:一个com.netflix.appinfo.LeaseInfo
对象,表示续约相关信息,见文下isCoordinatingDiscoveryServer
:是否是协调Server。默认是false,不用搭理metadata
:自定义元数据,可以是任何k-v- 关于eureka的元数据还是比较重要的,后有专门详解
lastUpdatedTimestamp
:上次修改时间lastDirtyTimestamp
:上次标记为Dirty的时间actionType
:动作类型。如ADDED/MODIFIED/DELETED
asgName
: 与此实例相关联 AWS自动缩放组名称- 此项配置是在AWS环境专门使用的实例启动,它已被用于流量停用后自动把一个实例退出服务。实际可忽略
version
:版本。默认值是unknown
,已标记过期,可不用搭理
构建方式
任何对象都需要构建嘛,哪怕是自动的。可以想到这么多属性,那必然采取的就是Builder方式构建喽。其实它提供了全参数的构造器,但那不会使用~
若我们自己构建,肯定这么玩:
InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()
.setInstanceId("account-001")
.setHostName("localhost")
.setIPAddr("127.0.0.1")
.setDataCenterInfo(new MyDataCenterInfo(DataCenterInfo.Name.MyOwn))
.setAppName("account") // 大小写无所谓
.build();
对于使用Builder构建也比较简单,但有如下方法特别指出,有一定小逻辑,可以稍加注意:
InstanceInfo.Builder
// 命名空间 一个InstanceInfo一个命名空间
// 默认的命名空间值是:eureka 这就是为何你的配置都是eureka.xxx的原因喽
private String namespace;
public Builder setNamespace(String namespace) {
this.namespace = namespace.endsWith(".") ? namespace : namespace + ".";
return this;
}
// 支持相对URL:explicitUrl 以及绝对URL:explicitUrl
// 相对URL会帮你拼接上http、端口等前缀
// 绝对url就帮你处理下占位符即可(若无占位符就不处理喽)
public Builder setHomePageUrl(String relativeUrl, String explicitUrl) {
String hostNameInterpolationExpression = "${" + namespace + "hostname}";
if (explicitUrl != null) {
result.homePageUrl = explicitUrl.replace(
hostNameInterpolationExpression, result.hostName);
} else if (relativeUrl != null) {
result.homePageUrl = HTTP_PROTOCOL + result.hostName + COLON
+ result.port + relativeUrl;
}
return this;
}
// 当然,你也可以来一个完全绝对的URL(并不太推荐)
public Builder setHomePageUrlForDeser(String homePageUrl) {
result.homePageUrl = homePageUrl;
return this;
}
... // 像什么setStatusPageUrl、setHealthCheckUrls等都支持这两种方式
// appName应用名称是必须的。其实还有InstanceId hostName等也都是必须的
public InstanceInfo build() {
if (!isInitialized()) {
throw new IllegalStateException("name is required!");
}
return result;
}
public boolean isInitialized() {
return (result.appName != null);
}
关于各URL的设值,推荐使用相对URL。另外,该实例的创建一般不通过手动显示各种肤质,而是通过配置的方式俩指定,具体可参见下篇文章EurekaInstanceConfig
配置接口。
属性读取
同样的道理,亦仅需关注其一些特殊方法而已:
InstanceInfo:
// key使用的是app
@JsonProperty("app")
public String getAppName() {
return appName;
}
// 返回实例的唯一id。并不是直接返回instanceId哦
@JsonIgnore
public String getId() {
// 若你自己配置了instanceId就直接返回
if (instanceId != null && !instanceId.isEmpty()) {
return instanceId;
// 否则取值字数据中心。
// 说明:`MyDataCenterInfo`实现了UniqueIdentifier
// AmazonInfo实现了此接口
} else if (dataCenterInfo instanceof UniqueIdentifier) {
String uniqueId = ((UniqueIdentifier) dataCenterInfo).getId();
if (uniqueId != null && !uniqueId.isEmpty()) {
return uniqueId;
}
}
return hostName;
}
@JsonIgnore
public boolean isPortEnabled(PortType type) {
if (type == PortType.SECURE) {
return isSecurePortEnabled;
} else {
return isUnsecurePortEnabled;
}
}
// 只要状态Status发生了变更,那么一定会setIsDirty();
// 标记此实例已经dirty了,需要中心注册
// 特别需要强调的是:此方法若set成功了,是有返回值的。返回的是prev之前的状态哦~
// 因此你可以通过返回值的是否为null俩判断InstanceInfo实例状态是否真的发生了变更
public synchronized InstanceStatus setStatus(InstanceStatus status) {
if (this.status != status) {
InstanceStatus prev = this.status;
this.status = status;
setIsDirty();
return prev;
}
return null;
}
// 当然也可以对dirty进行免疫
public synchronized void setStatusWithoutDirty(InstanceStatus status) {
if (this.status != status) {
this.status = status;
}
}
public synchronized void setOverriddenStatus(InstanceStatus status) {
if (this.overriddenStatus != status) {
this.overriddenStatus = status;
}
}
//增加元数据信息时,也会标记dirty了
// 它会被ApplicationInfoManager#registerAppMetadata调用
synchronized void registerRuntimeMetadata(Map<String, String> runtimeMetadata) {
metadata.putAll(runtimeMetadata);
setIsDirty();
}
另外,它还提供一个static的工具方法可以直接调用:获取该Info所在的可用区zone
InstanceInfo:
// 获取到当前实例InstanceInfo所在的zone区
// availZones:可用区(若为空就是default,否则就取第一个)
// 若实例使用的数据中心是Amazon类型,那就从其元数据里面拿出availabilityZone可用区(若配置了的话)
public static String getZone(String[] availZones, InstanceInfo myInfo) {
String instanceZone = ((availZones == null || availZones.length == 0) ? "default" : availZones[0]);
if (myInfo != null && myInfo.getDataCenterInfo().getName() == DataCenterInfo.Name.Amazon) {
String awsInstanceZone = ((AmazonInfo) myInfo.getDataCenterInfo())
.get(AmazonInfo.MetaDataKey.availabilityZone);
if (awsInstanceZone != null) {
instanceZone = awsInstanceZone;
}
}
return instanceZone;
}
逻辑总结为一句话:若你使用的是Amazon
数据中心类型,那么你可以通过元数据availabilityZone
来配置当前实例所在的zone(关于AmazonInfo
的元数据后文也有所讲解)。否则它所在的zone就是方法入参或者是defualt
。
LeaseInfo 续租信息
续租信息。续租是Eureka里特别重要的一个概念,Eureka会决定根据此租约中的EurekaInstanceConfig.getLeaseExpirationDurationInSeconds()
中设置的持续时间将实例从其视图中移除。租约还记录了上次续租的时间。
// 它的JSON形式表示形式是:{"leaseInfo":{xxx}}
@JsonRootName("leaseInfo")
public class LeaseInfo {
// 默认值们
public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;
public static final int DEFAULT_LEASE_DURATION = 90;
private int renewalIntervalInSecs = DEFAULT_LEASE_RENEWAL_INTERVAL;
private int durationInSecs = DEFAULT_LEASE_DURATION;
private long registrationTimestamp;
private long lastRenewalTimestamp;
private long evictionTimestamp;
private long serviceUpTimestamp;
}
renewalIntervalInSecs
:续租间隔时间(多长时间续约一次),默认是30s。- 用于Client客户端:每隔30s上报续约一次
durationInSecs
:续约持续时间(过期时间),默认是90s。90s倒计时,期间没有收到续约就会执行对应动作- 用于Server服务端,90s内木有收到心跳,就T除掉对应实例
registrationTimestamp
:租约的注册时间lastRenewalTimestamp
:最近一次的续约时间(服务端记录,用于倒计时的起始值)evictionTimestamp
:下线时间(服务的上、下线属于比较频繁的操作。但是此时服务实例并未T除去)serviceUpTimestamp
:上线时间
代码示例
创建一个InstanceInfo
这里就不手动Builder了,这里我使用Guice依赖注入的方式来得到一个实例:
@Test
public void fun5(){
Injector injector = Guice.createInjector(new EurekaModule());
InstanceInfo instance1 = injector.getInstance(InstanceInfo.class);
InstanceInfo instance2= injector.getInstance(InstanceInfo.class);
System.out.println(instance1.getId());
System.out.println(instance2.getId());
System.out.println(System.identityHashCode(instance1));
System.out.println(System.identityHashCode(instance2));
}
说明,因为我没有配置数据中心,因此我需要加个配置eureka.validateInstanceId = false
,这样运行程序,输出为:
2.0.0.2
2.0.0.2
1434234664
1434234664
what???竟然是单例。是的,InstanceInfo
全局仅需要一个,毕竟你一个应用就是一个实例嘛,那么为何呢???
究其原因就在这里:
@ProvidedBy(EurekaConfigBasedInstanceInfoProvider.class)
public class InstanceInfo { ... }
@Singleton // 单例
public class EurekaConfigBasedInstanceInfoProvider implements Provider<InstanceInfo> { ... }
在Spring Cloud
下使用的是InstanceInfoFactory
俩创建实例,然后交给ApplicationInfoManager
去管理的~
总结
关于Eureka的最核心概念:InstanceInfo实例信息就介绍到这了,别看仅是一个信息类,内容还真不少。本文对每个属性均做了介绍,希望能作为字典可以查询,可以帮助到你。
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |