[享学Eureka] 五、Eureka核心概念:应用(Application)和注册表(Applications)

信念和目标,必须永远洋溢在程序员内心。

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

通过前面文章我们已经了解了Eureka的核心概念之一:实例InstanceInfo,实例可以说是Eureka操作的最小单位。本文继续介绍其两个范围更广的概念:应用(Application)和注册表(Applications)。


正文

如果把实例类比于Java中的对象,那么应用Application就好比Class类,很明显Eureka管理着非常非常多的“类”,这便是它的注册表Applications


Application 应用

它代表一个应用:持有一堆的InstanceInfo实例。如Account应用,会对应着一对的account实例。


成员属性

@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
// 注意它的序列化/反序列化 {"application": {...}}
@XStreamAlias("application")
@JsonRootName("application")
public class Application {
	
	private static Random shuffleRandom = new Random();
	
    private String name;
    @XStreamOmitField
    private volatile boolean isDirty = false;
    @XStreamImplicit
    private final Set<InstanceInfo> instances;
    // 存储被打乱了的实例列表
    private final AtomicReference<List<InstanceInfo>> shuffledInstances;
    // 缓存实例id和InstanceInfo的对应关系
    private final Map<String, InstanceInfo> instancesMap;
	
	
	// 反序列化时通过此构造器来生成一个Application应用
    @JsonCreator
    public Application(@JsonProperty("name") String name,
            @JsonProperty("instance") List<InstanceInfo> instances) {
        this(name);
        for (InstanceInfo instanceInfo : instances) {
            addInstance(instanceInfo);
        }
    }

	// 关于name的设置提供了方法(带有缓存的)
    public void setName(String name) {
        this.name = StringCache.intern(name);
    }
}
  • Random shuffleRandom :Collections.shuffle()时传入,用于随机打乱infoList的顺序
  • name:应用名称 如:account
  • isDirty:标记该应用是否已经“脏”了。但凡改变了实例的个数(新增/减少)时就标记其脏了
  • instances:存储实例们的Set集合(自带去重,实例id相同代表同一实例)。默认是LinkedHashSet类型,具有顺序的
    • 该集合只能通过两个方法addInstance/removeInstance来改变其值
  • shuffledInstances:乱序后的实例集合。
  • instancesMap:缓存ConcurrentHashMap类型。key是实例id,value是对应的实例对象

成员方法

Application:

	// 添加一个实例(放在最上面。这是先remove后add的目的)
    public void addInstance(InstanceInfo i) {
        instancesMap.put(i.getId(), i);
        synchronized (instances) {
            instances.remove(i);
            instances.add(i);
            isDirty = true;
        }
    }
    // 移除一个  并且标注isDirty = true;
    public void removeInstance(InstanceInfo i) {
        removeInstance(i, true);
    }
    private void removeInstance(InstanceInfo i, boolean markAsDirty) {
        instancesMap.remove(i.getId());
        synchronized (instances) {
            instances.remove(i);
            if (markAsDirty) {
                isDirty = true;
            }
        }
    }

    @JsonIgnore
    public List<InstanceInfo> getInstancesAsIsFromEureka() {
        synchronized (instances) {
           return new ArrayList<InstanceInfo>(this.instances);
        }
    }
	// 返回所有的实例列表  注意:这里返回的是shuffledInstances乱序的Infos
	// 若乱序的为null,就返回正常顺序的:getInstancesAsIsFromEureka
    @JsonProperty("instance")
    public List<InstanceInfo> getInstances() {
        return Optional.ofNullable(shuffledInstances.get()).orElseGet(this::getInstancesAsIsFromEureka);
    }

	
	// 根据id拿到一个指定的Instance实例
    public InstanceInfo getByInstanceId(String id) {
        return instancesMap.get(id);
    }
    public int size() {
        return instances.size();
    }


	// 将应用程序中的实例列表打乱并将其存储用来检索
	// filterUpInstances:是否要过滤出来剩下InstanceStatus.UP的实例
	// indexByRemoteRegions:
    public void shuffleAndStoreInstances(boolean filterUpInstances) {
        _shuffleAndStoreInstances(filterUpInstances, false, null, null, null);
    }
    public void shuffleAndStoreInstances(Map<String, Applications> remoteRegionsRegistry,
                                         EurekaClientConfig clientConfig, InstanceRegionChecker instanceRegionChecker) {
        _shuffleAndStoreInstances(clientConfig.shouldFilterOnlyUpInstances(), true, remoteRegionsRegistry, clientConfig, instanceRegionChecker);
    }

这里唯一有疑问的是:为何要shuffle打乱呢?其实这和Eureka踢出实例是有关的,后文再专文分享。


Applications 注册表

该类用于封装由Eureka Server返回的所有注册表信息的类。


成员属性

@Serializer("com.netflix.discovery.converters.EntityBodyConverter")
@XStreamAlias("applications")
@JsonRootName("applications")
public class Applications {

    private static final String STATUS_DELIMITER = "_";

    private String appsHashCode;
    private Long versionDelta;
    @XStreamImplicit
    private final AbstractQueue<Application> applications;
    private final Map<String, Application> appNameApplicationMap;
    private final Map<String, VipIndexSupport> virtualHostNameAppMap;
    private final Map<String, VipIndexSupport> secureVirtualHostNameAppMap;


	// registeredApplications:已经注册的应用们
    @JsonCreator
    public Applications(@JsonProperty("appsHashCode") String appsHashCode,
            @JsonProperty("versionDelta") Long versionDelta,
            @JsonProperty("application") List<Application> registeredApplications) {
        this.applications = new ConcurrentLinkedQueue<Application>();
        this.appNameApplicationMap = new ConcurrentHashMap<String, Application>();
        this.virtualHostNameAppMap = new ConcurrentHashMap<String, VipIndexSupport>();
        this.secureVirtualHostNameAppMap = new ConcurrentHashMap<String, VipIndexSupport>();
        this.appsHashCode = appsHashCode;
        this.versionDelta = versionDelta;

		// 每个Application应用都添加进Map or Queue里面去存储着
        for (Application app : registeredApplications) {
            this.addApplication(app);
        }
    }
}
  • STATUS_DELIMITER:作为getReconcileHashCode的分隔符
  • appsHashCode:由eureka服务器使用。不可外用。为应用集合分配的HashCode散列值。该值一般来自于getReconcileHashCode()方法
  • versionDelta:已过期属性,不用搭理
  • applications:包含的所有Application应用们。使用AbstractQueue装载,实际是个ConcurrentLinkedQueue队列(特点:FIFO)
  • appNameApplicationMap:map缓存。key是应用名,value是应用实例本身。
  • virtualHostNameAppMap:map缓存。key是InstanceInfo.vipAddress,value是VipIndexSupport(持有多个InstanceInfo实例)
    • 说明:vipAddress为null是不会放进Map里去的
  • secureVirtualHostNameAppMap:同上。区别是它用的InstanceInfo#secureVipAddress做key
    • 说明:vipAddress为null是不会放进Map里去的

成员方法

Applications:

	// 添加一个应用
    public void addApplication(Application app) {
        appNameApplicationMap.put(app.getName().toUpperCase(Locale.ROOT), app);
        addInstancesToVIPMaps(app, this.virtualHostNameAppMap, this.secureVirtualHostNameAppMap);
        applications.add(app);
    }
    // 移除一个Application应用
    public void removeApplication(Application app) {
        this.appNameApplicationMap.remove(app.getName().toUpperCase(Locale.ROOT));
        this.applications.remove(app);
    }

	// 得到已经注册的Applications应用们
    @JsonProperty("application")
    public List<Application> getRegisteredApplications() {
        return new ArrayList<>(this.applications);
    }
    // 根据应用名获取应用
    public Application getRegisteredApplications(String appName) {
        return appNameApplicationMap.get(appName.toUpperCase(Locale.ROOT));
    }

	// 注意:这是获取所有应用的instances实例总和,并不是应用综合哦~~~~~
    public int size() {
        return applications.stream().mapToInt(Application::size).sum();
    }

	// 获取此**应用程序实例们**的哈希代码。用于比较eureka服务器和eureka客户端之间的实例。
	// 一致性hash   Reconcile:一致性
	// hash值会通过STATUS_DELIMITER来拼接~~~~~~
    @JsonIgnore
    public String getReconcileHashCode() {
        TreeMap<String, AtomicInteger> instanceCountMap = new TreeMap<String, AtomicInteger>();
        populateInstanceCountMap(instanceCountMap);
        return getReconcileHashCode(instanceCountMap);
    }

	// 打乱InstanceInfo实例们
	// 针对每个Application都会打乱,内部依赖于Application#shuffleAndStoreInstances实现的
    public void shuffleInstances(boolean filterUpInstances) {
        shuffleInstances(filterUpInstances, false, null, null, null);
    }
    public void shuffleAndIndexInstances(Map<String, Applications> remoteRegionsRegistry,
            							EurekaClientConfig clientConfig, InstanceRegionChecker instanceRegionChecker) {
        shuffleInstances(clientConfig.shouldFilterOnlyUpInstances(), true, remoteRegionsRegistry, clientConfig, instanceRegionChecker);
    }

总之,Applications包装Eureka服务器返回的所有注册表信息的类。

需要注意的是:注册表信息是从EurekaClientConfig.getRegistryFetchIntervalSeconds()中指定的eureka Server取的(也就是这些Application们)。获取信息后,将对其进行洗牌打乱,并对配置EurekaClientConfig.shouldFilterOnlyUpInstances()指定的InstanceInfo.InstanceStatus.UP状态的实例进行筛选。


总结

关于Eureka的核心概念:应用(Application)和注册表(Applications)就先介绍这,到此它的三大核心概念:实例、应用、注册表就介绍完了。简单的说,Eureka就是围绕这“三大概念”制定策略,完成“CRUD”操作的。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了370 篇原创文章 · 获赞 548 · 访问量 49万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/105142519