这句话不是很文雅:彻底鄙视那些害怕别人超越自己而拒绝回答别人问题的程序员。
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning
前言
前面文章已经介绍了InstanceInfo
,知道了它是个不带行为的近似POJO类,特点是属性特别的多,因此直接使用起来挺笨重的且多有不便。在加上它并没有重要逻辑的管理,因此本文介绍一个它的管理类:ApplicationInfoManager
,以后你若要操作InstanceInfo
一般均可通过它来完成。
小吐槽一句:该类命名为InstanceInfoManager其实更为合适。因为它管理的是
InstanceInfo
实例而非应用Application。
正文
ApplicationInfoManager
是一个综合类,它有多个静态内部接口/类:
StatusChangeListener
:实例状态监听器,当实例状态发生改变时会触发
public static interface StatusChangeListener {
String getId();
void notify(StatusChangeEvent statusChangeEvent);
}
InstanceStatusMapper
:实例状态的映射。函数式接口:指定一个map映射策略,在设置新状态的时候经其映射得到最终状态
public static interface InstanceStatusMapper {
InstanceStatus map(InstanceStatus prev);
}
该状态规则/策略由外部指定,否则即为默认策略(原样输出):
private static final InstanceStatusMapper NO_OP_MAPPER = prev -> prev;
OptionalArgs
:说白了,它就是对InstanceStatusMapper
进行Optional的一个包装
public static class OptionalArgs {
private InstanceStatusMapper instanceStatusMapper;
@com.google.inject.Inject(optional = true)
public void setInstanceStatusMapper(InstanceStatusMapper instanceStatusMapper) {
this.instanceStatusMapper = instanceStatusMapper;
}
InstanceStatusMapper getInstanceStatusMapper() {
return instanceStatusMapper == null ? NO_OP_MAPPER : instanceStatusMapper;
}
}
另外此处说句题外话:从代码处可见它一会使用标准的javax.inject.Inject
注解,一会用Google Guice自家的Inject
注解,这也是Spring Cloud没法直接全盘接过来其内置的DI依赖的原因(SC自己完全实现了一套基于Spring的依赖管理来整合Eureka的)。
ApplicationInfoManager
我们知道InstanceInfo
是全局独一份,而实例的状态InstanceStatus
是很重要的一个指标,而这个指标就是通过此管理器来管理的。
成员属性
@Singleton
public class ApplicationInfoManager {
private static ApplicationInfoManager instance = new ApplicationInfoManager(null, null, null);
// please use DI instead
@Deprecated
public static ApplicationInfoManager getInstance() {
return instance;
}
protected final Map<String, StatusChangeListener> listeners;
private final InstanceStatusMapper instanceStatusMapper;
private InstanceInfo instanceInfo;
private EurekaInstanceConfig config;
// 初始化一个ApplicationInfoManager对象
// OptionalArgs目的就是传递一个`InstanceStatusMapper`实例
@Inject
public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo, OptionalArgs optionalArgs) {
this.config = config;
this.instanceInfo = instanceInfo;
this.listeners = new ConcurrentHashMap<>();
if (optionalArgs != null) {
this.instanceStatusMapper = optionalArgs.getInstanceStatusMapper();
} else {
this.instanceStatusMapper = NO_OP_MAPPER;
}
// 把通过DI生成的实例重新赋值给它,已达到统一的效果
instance = this;
}
}
instance
:单例模式嘛。不过已经不推荐使用了,而推荐你使用DI来获得该单例- 在Spring Cloud下由
EurekaClientAutoConfiguration
负责创建单例
- 在Spring Cloud下由
Map<String, StatusChangeListener> listeners
:key是StatusChangeListener
的idStatusChangeListener
是一个ApplicationInfoManager
的静态内部类:用于监听StatusChangeEvent
事件StatusChangeEvent
事件代表着InstanceInfo实例状态发生了变更,它有且仅有两个地方发送此事件:ApplicationInfoManager#setInstanceStatus
:若状态发生了变更便会发送此事件,从而触发StatusChangeListener
监听器DiscoveryClient#onRemoteStatusChanged
:发送StatusChangeEvent
事件。但是,但是,但是触发的监听器是EurekaEventListener
。- 该监听器监听的
EurekaEvent
事件,而StatusChangeEvent
继承自EurekaEvent
EurekaEvent
有且仅有两个实现类:StatusChangeEvent
和CacheRefreshedEvent
(该事件在和Ribbon整合的时候会用到,因为缓存变了,Ribbon的服务列表也得跟着变~)
- 该监听器监听的
InstanceStatusMapper instanceStatusMapper
:一个函数式接口public InstanceStatus map(InstanceStatus prev)
用于根据prev之前的状态做个map映射。它唯一被调用的地方是在setInstanceStatus
里,具体的map规则由调用者指定InstanceInfo instanceInfo
:管理的实例(毕竟实例也是单例嘛~)EurekaInstanceConfig config
:管理的实例配置
成员方法
ApplicationInfoManager:
// 如果你使用getInstance()得到的实例,记得调用它完成初始化
// 如果是DI方式,就不用啦
public void initComponent(EurekaInstanceConfig config) {
try {
this.config = config;
this.instanceInfo = new EurekaConfigBasedInstanceInfoProvider(config).get();
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize ApplicationInfoManager", e);
}
}
// 获取管理的实例
public InstanceInfo getInfo() {
return instanceInfo;
}
public EurekaInstanceConfig getEurekaInstanceConfig() {
return config;
}
// 给实例**添加** 最终会进入到InstanceInfo#metadata元数据的Map里
public void registerAppMetadata(Map<String, String> appMetadata) {
instanceInfo.registerRuntimeMetadata(appMetadata);
}
// =================最重要的一个方法=================
// 设置此实例的状态。应用程序可以使用它来指示是否准备接收流量。
// 在这里设置状态也会通知所有已注册的侦听器状态更改事件的。
// 如果你是自己通过InstanceInfo.setStatus去设置状态,那监听器啥的都不会触发的~~~~~
// 另外:设进来的状态需要经过instanceStatusMapper策略处理一下(默认策略是不处理)
public synchronized void setInstanceStatus(InstanceStatus status) {
InstanceStatus next = instanceStatusMapper.map(status);
if (next == null) {
return;
}
// 如果prev不为null,那就证明状态发生了变更,就要触发变更的监听器
InstanceStatus prev = instanceInfo.setStatus(next);
if (prev != null) {
for (StatusChangeListener listener : listeners.values()) {
try {
listener.notify(new StatusChangeEvent(prev, next));
} catch (Exception e) {
logger.warn("failed to notify listener: {}", listener.getId(), e);
}
}
}
}
// 注册状态变更监听器
// DiscoveryClient里会通过此方法注册一个监听器来监控实例状态:若状态变更变为Down了就立马同步给Server
public void registerStatusChangeListener(StatusChangeListener listener) {
listeners.put(listener.getId(), listener);
}
public void unregisterStatusChangeListener(String listenerId) {
listeners.remove(listenerId);
}
// 重新获取主机名以检查它是否已更改 也就是维护实例的setHostName、setIPAddr
// 说明:只有当你数据中心使用的AmazonInfo
// 或者 是RefreshableInstanceConfig动态配置时,才有可能有newAddress的可能,否则不可能变的
public void refreshDataCenterInfoIfRequired() { ... }
// 续租信息同步一把,若有需要的话。
// 这主要考虑到`EurekaInstanceConfig`可能是个可动态刷新的实例,所以通过这个方法去判断一把是否需要同步
// 这两个方法的调用处是:DiscoveryClient#refreshInstanceInfo
public void refreshLeaseInfoIfRequired() { ... }
作用总结
对ApplicationInfoManager
有何用做出总结:
- 对全局唯一实例
InstanceInfo
进行管理:- 管理的实例一般由构造器传入,亦可由
new EurekaConfigBasedInstanceInfoProvider(config).get()
获取 - 通过
registerAppMetadata()
方法为InstanceInfo
的元数据添加k-v数据 - 提供public方法
setInstanceStatus()
为InstanceInfo
设置新的InstanceStatus
实例状态InstanceInfo#setStatus
自己的设置方法的唯一调用处就是在此,因此请勿直接调用InstanceInfo#setStatus
来更改实例的状态,那太偏底层了- 通过此方法设置状态,若状态有变化,便会触发
StatusChangeListener
监听器,从而进行后续一些列的逻辑(如:立马同步状态给Server)
- 管理的实例一般由构造器传入,亦可由
- 提供注册、取消注册用于监控
InstanceInfo
实例的监听器StatusChangeListener
的方法 - 提供
refreshDataCenterInfoIfRequired()
方法用于刷新/同步数据中心信息 - 提供
refreshLeaseInfoIfRequired()
方法用于刷新/同步LeaseInfo
信息
总结
关于InstanceInfo实例的管理器:ApplicationInfoManager就先介绍到这。最后我想特别说明的是:ApplicationInfoManager
也是单例,推荐使用DI来初始化该单例。非Spring环境使用内置的Guice去完成,Spring Cloud
下就使用它自己的DI容器完成喽。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。