信念和目标,必须永远洋溢在程序员内心。
–> 返回专栏总目录 <–
代码下载地址: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
:应用名称 如:accountisDirty
:标记该应用是否已经“脏”了。但凡改变了实例的个数(新增/减少)时就标记其脏了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高工、架构师 系列群大家庭学习和交流。