Spring Cloud Eureka源码分析---服务注册
本篇我们着重分析Eureka服务端的逻辑实现,主要涉及到服务的注册流程分析。
在Eureka的服务治理中,会涉及到下面一些概念:
服务注册:Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如 IP 地址、端口、运行状况指标的URL、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个ConcurrentHashMap中。
服务续约:在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒发送一次心跳来进行服务续约。
服务同步:Eureka Server之间会互相进行注册,构建Eureka Server集群,不同Eureka Server之间会进行服务同步,用来保证服务信息的一致性。
获取服务:服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
服务调用:服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone,在进行服务调用时,优先访问处于同一个Zone中的服务提供者。
服务下线:当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
服务剔除:有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒)的服务剔除。
自我保护:既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。
1. 基本原理:
- Eureka Server 提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到;
- Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器;
- 在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒),如果Eureka Server在多个心跳周期没有收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认90秒);
- Eureka Server之间将会通过复制的方式完成数据的同步;
- Eureka Client具有缓存的机制,即使所有的Eureka Server 都挂掉的话,客户端依然可以利用缓存中的信息消费其它服务的API;
上一篇中我们搭建了一个简单的Eureka客户端和服务端。如果你有启动过观看启动日志不难发现:

这里有个EurekaServerBootstrap类,启动日志中给出:Setting the eureka configuration..,Initialized server context。看起来这个应该是个启动类,跟进去看一下,有个很显眼的方法:

这个方法的调用先按住不表,我们先从启动类上添加的 EnableEurekaServer注解着手,看看为什么添加了一个注解就能激活 Rureka。
从server启动类上的EnableEurekaServer注解进入:
接下来引用了
EurekaServerMarkerConfiguration,看到在这个注解上有个注释:启用这个注解的目的是为了激活:EurekaServerAutoConfiguration类;进入EurekaServerAutoConfiguration看到在类头部有一个注解:
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
即
EurekaServerAutoConfiguration启动的条件是EurekaServerMarkerConfiguration注解先加载。

上面这一张图标识出了从启动注解到预启动类的流程,但是你会发现实际上 EurekaServerAutoConfiguration 也没有做什么事情:配置初始化,启动一些基本的过滤器。同样在类头部的引用上有一个Import注解:
@Import(EurekaServerInitializerConfiguration.class)
所以在 EurekaServerAutoConfiguration 初始化的时候,会引用到 EurekaServerInitializerConfiguration,激活它的初始化。EurekaServerInitializerConfiguration 实现了SmartLifecycle.start方法,在spring 初始化的时候会被启动,激活 run 方法。可以看到在 run 方法中调用的就是:
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
即我们上面截图中的EurekaServerBootstrap.contextInitialized()方法。
整体的调用流程如下:

具体的初始化信息见下图:

2. 服务注册实现
2.1 server端启动时同步别的server上的client
在上面讲到Eureka server启动过程中,启动一个Eureka Client的时候,initEurekaServerContext()里面会进行服务同步和服务剔除,syncUp()方法所属的类是:PeerAwareInstanceRegistry,即server端的服务注册逻辑都在这里面。因为没有使用AWS的服务器,所以默认实例化的实现类为:PeerAwareInstanceRegistryImpl。
PeerAwareInstanceRegistry registry;
if (isAws(applicationInfoManager.getInfo())) {
registry = new AwsInstanceRegistry(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
awsBinder.start();
} else {
registry = new PeerAwareInstanceRegistryImpl(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
}
PeerAwareInstanceRegistryImpl 继承了一个抽象类 AbstractInstanceRegistry:
@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
}
AbstractInstanceRegistry中的实现逻辑是真正的服务注册存储所在地:
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
private static final Logger logger = LoggerFactory.getLogger(AbstractInstanceRegistry.class);
private static final String[] EMPTY_STR_ARRAY = new String[0];
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
protected Map<String, RemoteRegionRegistry> regionNameVSRemoteRegistry = new HashMap<String, RemoteRegionRegistry>();
protected final ConcurrentMap<String, InstanceStatus> overriddenInstanceStatusMap = CacheBuilder
.newBuilder().initialCapacity(500)
.expireAfterAccess(1, TimeUnit.HOURS)
.<String, InstanceStatus>build().asMap();
....
....
....
}
所有的服务实例信息都保存在 server 本地的map当中。所以在server端启动的时候会去拉别的server上存储的client实例,然后存储到本地缓存。
2.2 client主动注册
如果是某个client主动发出了注册请求,那么是如何注册到服务端呢?
还是查看日志:启动服务端,然后再启动客户端,查看服务端日志:

这里能看到刚才启动的客户端已经在服务端注册了,注册逻辑走的类是:AbstractInstanceRegistry。
上面也提到 是服务注册的逻辑实现类,完成保存客户端信息的方法是:
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
......
}
代码就不贴了,主要实现的逻辑是保存当前注册的客户端信息。我们知道客户端是发送了一次http请求给服务端,那么真正的注册逻辑应该是从一个http请求的接收处进来的。跟着使用了register方法的地方去找,PeerAwareInstanceRegistryImpl里面有调用:
@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();
}
super.register(info, leaseDuration, isReplication);
//将新节点信息告诉别的服务端
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
这里没有改写父类的register逻辑,下面还多了一句:replicateToPeers,这里主要做的逻辑是:给兄弟 server节点发送register 请求,告诉他们有客户端来注册。
继续看谁调用了这里,可以找到:ApplicationResource 的addInstance方法调用了:
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
......
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
而这里很显然是一个接口,逻辑就很清晰了:

另外,我们查看addInstance方法被谁调用的过程中发现:PeerReplicationResource--->batchReplication 方法也调用了注册的逻辑。
这个方法一看竟然解答了之前我的疑惑:服务端之间是如何发送心跳的。原来实现是在这里。通过dispatch方法来区分当前的调用是何种请求,
可以看到,服务注册,心跳检测,服务取消,服务下线,服务剔除的入口都在这里:
@Path("batch")
@POST
public Response batchReplication(ReplicationList replicationList) {
try {
ReplicationListResponse batchResponse = new ReplicationListResponse();
for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {
try {
batchResponse.addResponse(dispatch(instanceInfo));
} catch (Exception e) {
batchResponse.addResponse(new ReplicationInstanceResponse(Status.INTERNAL_SERVER_ERROR.getStatusCode(), null));
logger.error("{} request processing failed for batch item {}/{}",
instanceInfo.getAction(), instanceInfo.getAppName(), instanceInfo.getId(), e);
}
}
return Response.ok(batchResponse).build();
} catch (Throwable e) {
logger.error("Cannot execute batch Request", e);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {
ApplicationResource applicationResource = createApplicationResource(instanceInfo);
InstanceResource resource = createInstanceResource(instanceInfo, applicationResource);
String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());
String overriddenStatus = toString(instanceInfo.getOverriddenStatus());
String instanceStatus = toString(instanceInfo.getStatus());
Builder singleResponseBuilder = new Builder();
switch (instanceInfo.getAction()) {
case Register:
singleResponseBuilder = handleRegister(instanceInfo, applicationResource);
break;
case Heartbeat:
singleResponseBuilder = handleHeartbeat(serverConfig, resource, lastDirtyTimestamp, overriddenStatus, instanceStatus);
break;
case Cancel:
singleResponseBuilder = handleCancel(resource);
break;
case StatusUpdate:
singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);
break;
case DeleteStatusOverride:
singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);
break;
}
return singleResponseBuilder.build();
}
从这个入口进去,大家可以跟踪一下感兴趣的逻辑。
Spring Cloud Eureka源码分析---服务注册的更多相关文章
- Spring Cloud Eureka源码分析 --- client 注册流程
Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的.使用轮询负载算法的负载均衡器. 在应用启动后,将会向Eureka Serve ...
- Spring Cloud Eureka源码分析之服务注册的流程与数据存储设计!
Spring Cloud是一个生态,它提供了一套标准,这套标准可以通过不同的组件来实现,其中就包含服务注册/发现.熔断.负载均衡等,在spring-cloud-common这个包中,org.sprin ...
- Spring Cloud Eureka源码分析之三级缓存的设计原理及源码分析
Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中,实现原理如下图所示. 第一层缓存:readOnlyCache ...
- Spring Cloud Eureka源码分析之心跳续约及自我保护机制
Eureka-Server是如何判断一个服务不可用的? Eureka是通过心跳续约的方式来检查各个服务提供者的健康状态. 实际上,在判断服务不可用这个部分,会分为两块逻辑. Eureka-Server ...
- Spring Cloud Eureka 分布式开发之服务注册中心、负载均衡、声明式服务调用实现
介绍 本示例主要介绍 Spring Cloud 系列中的 Eureka,使你能快速上手负载均衡.声明式服务.服务注册中心等 Eureka Server Eureka 是 Netflix 的子模块,它是 ...
- 【spring cloud】源码分析(一)
概述 从服务发现注解 @EnableDiscoveryClient入手,剖析整个服务发现与注册过程 一,spring-cloud-common包 针对服务发现,本jar包定义了 DiscoveryCl ...
- Spring Cloud Ribbon源码分析---负载均衡实现
上一篇结合 Eureka 和 Ribbon 搭建了服务注册中心,利用Ribbon实现了可配置负载均衡的服务调用.这一篇我们来分析Ribbon实现负载均衡的过程. 从 @LoadBalanced入手 还 ...
- Spring Cloud Eureka 4 (高可用服务注册中心)
在微服务这样的分布式环境中,我们需要充分考虑发生故障的情况,所以在生产环境中必须考虑对各个组件进行高可用部署,对于服务注册中心也是一样. Eureka Server 的高可用实际上就是讲自己作为服务向 ...
- Spring Cloud Ribbon 源码分析---负载均衡算法
上一篇分析了Ribbon如何发送出去一个自带负载均衡效果的HTTP请求,本节就重点分析各个算法都是如何实现. 负载均衡整体是从IRule进去的: public interface IRule{ /* ...
随机推荐
- ps查看图层大小快捷键
1.图层大小尺寸的: ctrl+alt+c 2.图片大小尺寸.像素大小: ctrl+alt+i
- thymeleaf 语法
一.语法: 1. 简单表达式 (simple expressions) ${...} 变量表达式 *{...} 选择变量表达式 #{...} 消息表达式 @{...} 链接url表达式 2.字 ...
- Elasticsearch实战-磁盘IO被打满
背景 事情是这样的.一天下午4点42分左右.业务反馈我开发的服务在测试环境出现问题,返回资源数据是0.查日志发现是ES访问超时.相当于数据库挂了.持续了20多分钟自己恢复.咨询了ES团队,最终得到下面 ...
- Android适配总结
1.dp与px的 密度类型 代表的分辨率 屏幕密度 换算 比例 低密度(ldpi) 240x320 120 1dp=0.75px 3 中密度(mdpi) 320x480 160 1dp = 1px 4 ...
- ACM-图论-同余最短路
https://www.cnblogs.com/31415926535x/p/11692422.html 一种没见过的处理模型,,记录一下,,主要是用来处理一个多元一次方程的解的数量的问题,,数据量小 ...
- idea配置maven以及手动添加webapp目录
idea配置maven 点击右下角Configure 点击settings 3 . 设置路径 设置自动导包 4 . 点击创建新工程 5 . 选择maven点击下一步 6 . 7 . 8 . 此时,创建 ...
- 阿里云 RDS 数据库又发 CPU 近 100% 的“芯脏病”
最近云界发生了2件事,一件是大事,一件是小事,大事是阿里云与微软合作推出了开放应用模型 Open Application Model(OAM),小事是由于微软 SQL Server 在阿里云上水土不服 ...
- Win10安装gcc、g++、make
下载安装包: 1. MinGW官网下载:http://www.mingw.org 点击右上角Downloads 点击下载 mingw-get-setup.exe 2. 百度网盘(2 ...
- 代码审计-DedeCMS-V5.7前台任意用户密码重置
0x01 漏洞影响 该漏洞允许攻击者修改任意前台用户密码. 0x02 漏洞利用条件 1,开启会员模块 2,攻击者拥有一个正常的会员账号 3,目标没有设置安全问题 0x03 漏洞分析 漏洞文件:/mem ...
- 《Java并发编程实战》读书笔记-第5章 基础构建模块
同步容器类 同步容器类实现线程安全的方式:将所有状态封装起来,对每个公有方法使用同步,使得每一次只有一个线程可以访问.同步容器类包含:Vector.Hashtable.Collections.sync ...