【源码】Spring Cloud —— Eureka Server 2 集群相关
前言
上一章节,对 AbstractInstanceRegistry 实现 服务注册、服务续约、服务下线、服务剔除 的相关内容做了解读,本章节对 Eureka 集群相关的操作进行解读
版本
Spring Cloud Netflix 版本:2.2.3.RELEASE
对应 Netflix-Eureka 版本:1.9.21
集群初始化
Eureka Server 在启动时,首先要尝试从其他节点获取注册表信息,该逻辑由 PeerAwareInstanceRegistryImpl#syncUp
方法实现
@Override
public int syncUp() {
int count = 0;
// 尝试多次
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
// 每次失败后会睡眠一会
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
// ...
break;
}
}
// 获取所有 InstanceInfo 并注册
Applications apps = eurekaClient.getApplications();
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
try {
if (isRegisterable(instance)) {
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
// 注册成功则 count++,打断循环条件
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
该方法的调用链路如下
spring-cloud-netflix-eureka-server
依赖通过 自动装配 引入类 EurekaServerAutoConfiguration- EurekaServerAutoConfiguration 通过注解
@Import
引入类 EurekaServerInitializerConfiguration - EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,再其
start
生命周期中调用了eurekaServerBootstrap#contextInitialized
方法 - 该方法内部调用
syncUp
- 在
syncUp
之后,openForTraffic
方法会被调用,使得 Eureka Server 可以接受来自 Eureka Client 的流量信息
集群复制
Eureka Server 的 服务注册、服务续约、服务下线、服务剔除 等操作也会同步到其他集群上
比如 服务注册 PeerAwareInstanceRegistryImpl#register
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// AbstractInstanceRegistry#register
super.register(info, leaseDuration, isReplication);
// 集群同步
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
---------------- replicateToPeers ----------------
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info,
InstanceStatus newStatus, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// 避免多次复制
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
// 遍历所有集群
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// 当然首先排除给自己复制
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
// 复制
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
---------- replicateInstanceActionsToPeers ----------
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry;
CurrentRequestVersion.set(Version.V2);
// 根据对应的 Action 调用对应的方法
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
// ...
} finally {
CurrentRequestVersion.remove();
}
}
借由 集群初始化 和 集群复制 的操作,Eureka 保证各集群间的数据同步,保证了 分区容忍性
实例获取
- Application:封装 服务 的 实例 列表,所有的 实例 都挂在相同的
appName
下 - Applications:注册表中 所有服务 的集合
注册表以 Application 维度封装了 InstanceInfo,AbstractInstanceRegistry 类提供了对应的获取方法
getApplication
@Override
public Application getApplication(String appName) {
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
return this.getApplication(appName, !disableTransparentFallback);
}
@Override
public Application getApplication(String appName, boolean includeRemoteRegion) {
Application app = null;
// 所有租约
Map<String, Lease<InstanceInfo>> leaseMap = registry.get(appName);
if (leaseMap != null && leaseMap.size() > 0) {
// 将所有租约的实例信息封装到 Application
for (Entry<String, Lease<InstanceInfo>> entry : leaseMap.entrySet()) {
if (app == null) {
app = new Application(appName);
}
app.addInstance(decorateInstanceInfo(entry.getValue()));
}
} else if (includeRemoteRegion) {
// 尝试从远程 Region 获取
for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) {
Application application = remoteRegistry.getApplication(appName);
if (application != null) {
return application;
}
}
}
return app;
}
参数 includeRemoteRegion
决定是否从远程 Region 获取
getApplications
public Applications getApplications() {
boolean disableTransparentFallback = serverConfig.disableTransparentFallbackToOtherRegion();
if (disableTransparentFallback) {
// 仅从本地
return getApplicationsFromLocalRegionOnly();
} else {
// 包含其他 Region
return getApplicationsFromAllRemoteRegions();
}
}
---------------------------------------------------
public Applications getApplicationsFromAllRemoteRegions() {
return getApplicationsFromMultipleRegions(allKnownRemoteRegions);
}
@Override
public Applications getApplicationsFromLocalRegionOnly() {
return getApplicationsFromMultipleRegions(EMPTY_STR_ARRAY);
}
-------- getApplicationsFromMultipleRegions --------
public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) {
boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0;
// ...
if (includeRemoteRegion) {
GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment();
} else {
GET_ALL_CACHE_MISS.increment();
}
Applications apps = new Applications();
apps.setVersion(1L);
// 遍历所有租约信息
for (Entry<String, Map<String, Lease<InstanceInfo>>> entry : registry.entrySet()) {
Application app = null;
if (entry.getValue() != null) {
// 遍历所有示例信息封装到 Application
for (Entry<String, Lease<InstanceInfo>> stringLeaseEntry : entry.getValue().entrySet()) {
Lease<InstanceInfo> lease = stringLeaseEntry.getValue();
if (app == null) {
app = new Application(lease.getHolder().getAppName());
}
app.addInstance(decorateInstanceInfo(lease));
}
}
// 将 Application 封装到 Applications
if (app != null) {
apps.addApplication(app);
}
}
if (includeRemoteRegion) {
// 遍历所有远程注册表
for (String remoteRegion : remoteRegions) {
RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion);
if (null != remoteRegistry) {
// 获取对应的 Applications
Applications remoteApps = remoteRegistry.getApplications();
// 遍历 Applications 中的所有 Application
for (Application application : remoteApps.getRegisteredApplications()) {
if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) {
// ...
// 从 apps 中获取对应的 Application
Application appInstanceTillNow = apps.getRegisteredApplications(application.getName());
// 获取不到则将其加入
if (appInstanceTillNow == null) {
appInstanceTillNow = new Application(application.getName());
apps.addApplication(appInstanceTillNow);
}
// 添加对应的实例信息
for (InstanceInfo instanceInfo : application.getInstances()) {
appInstanceTillNow.addInstance(instanceInfo);
}
} else {
// ...
}
}
} else {
// ...
}
}
}
apps.setAppsHashCode(apps.getReconcileHashCode());
return apps;
}
提供对所有 服务实例 的获取
总结
PeerAwareInstanceRegistryImpl 和 AbstractInstanceRegistry 提供相关的操作保证了 Eureka Server 各集群间 服务实例 的同步,因此保证 Eureka Server 作为 注册中心 的 分区容忍性
关于 Eureka 和 CAP
关于 分布式系统 的 CAP 理论,可以看这篇文章
Eureka 便是典型的 AP 系统
- A:可用性(Availability),Eureka 通过 自我保护 机制,在由于 Eureka Client 和 Eureka Server 之间通讯中断时引起大面积 服务心跳 丢失时,便会启动 自我保护 机制,避免大批健康的 服务实例 被剔除,这大大提升了 Eureka 的 可用性
- P:分区容忍性(Partition Tolerance),Eureka 并不依赖单一的 Server,Eureka Client 和 Eureka Server 直接可以互相注册,形成 集群,通过 集群 之间 服务实例 信息的 复制与同步,单一 Eureka Server 的宕机并不会对 服务 的访问产生影响,这大大提升了 Eureka 的 分区容忍性
- C:一致性(Consistency),由于 集群 之间的 同步复制 是通过 HTTP 的方式进行,基于网络的不可靠性,集群 中的 Eureka Server 之间的注册表信息难免存在不同步的时间节点,因此不满足 一致性
上一篇:【源码】Spring Cloud —— Eureka Server 1 服务注册、服务续约、服务下线、服务剔除
参考
《Spring Cloud 微服务架构进阶》 —— 朱荣鑫 张天 黄迪璇