前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Eureka] 六、InstanceInfo实例管理器:ApplicationInfoManager

[享学Eureka] 六、InstanceInfo实例管理器:ApplicationInfoManager

作者头像
YourBatman
发布2020-04-01 17:43:22
1.5K0
发布2020-04-01 17:43:22
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

代码下载地址:https://github.com/f641385712/netflix-learning

前言

前面文章已经介绍了InstanceInfo,知道了它是个不带行为的近似POJO类,特点是属性特别的多,因此直接使用起来挺笨重的且多有不便。在加上它并没有重要逻辑的管理,因此本文介绍一个它的管理类:ApplicationInfoManager,以后你若要操作InstanceInfo一般均可通过它来完成。

小吐槽一句:该类命名为InstanceInfoManager其实更为合适。因为它管理的是InstanceInfo实例而非应用Application。


正文

ApplicationInfoManager是一个综合类,它有多个静态内部接口/类:

  • StatusChangeListener:实例状态监听器,当实例状态发生改变时会触发
代码语言:javascript
复制
public static interface StatusChangeListener {
    String getId();
    void notify(StatusChangeEvent statusChangeEvent);
}
  • InstanceStatusMapper:实例状态的映射。函数式接口:指定一个map映射策略,在设置新状态的时候经其映射得到最终状态
代码语言:javascript
复制
public static interface InstanceStatusMapper {
    InstanceStatus map(InstanceStatus prev);
}

该状态规则/策略由外部指定,否则即为默认策略(原样输出):

代码语言:javascript
复制
private static final InstanceStatusMapper NO_OP_MAPPER = prev -> prev;
  • OptionalArgs:说白了,它就是对InstanceStatusMapper进行Optional的一个包装
代码语言:javascript
复制
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是很重要的一个指标,而这个指标就是通过此管理器来管理的。


成员属性
代码语言:javascript
复制
@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负责创建单例
  • Map<String, StatusChangeListener> listeners:key是StatusChangeListener的id
    • StatusChangeListener是一个ApplicationInfoManager的静态内部类:用于监听StatusChangeEvent事件
    • StatusChangeEvent事件代表着InstanceInfo实例状态发生了变更,它有且仅有两个地方发送此事件:
      • ApplicationInfoManager#setInstanceStatus:若状态发生了变更便会发送此事件,从而触发StatusChangeListener监听器
      • DiscoveryClient#onRemoteStatusChanged:发送StatusChangeEvent事件。但是,但是,但是触发的监听器是EurekaEventListener
        • 该监听器监听的EurekaEvent事件,而StatusChangeEvent继承自EurekaEvent
        • EurekaEvent有且仅有两个实现类:StatusChangeEventCacheRefreshedEvent(该事件在和Ribbon整合的时候会用到,因为缓存变了,Ribbon的服务列表也得跟着变~)
  • InstanceStatusMapper instanceStatusMapper:一个函数式接口public InstanceStatus map(InstanceStatus prev)用于根据prev之前的状态做个map映射。它唯一被调用的地方是在setInstanceStatus里,具体的map规则由调用者指定
  • InstanceInfo instanceInfo:管理的实例(毕竟实例也是单例嘛~)
  • EurekaInstanceConfig config:管理的实例配置

成员方法
代码语言:javascript
复制
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容器完成喽。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文
    • ApplicationInfoManager
      • 成员属性
      • 成员方法
      • 作用总结
  • 总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档