Spring Cloud Eureka服务注册源码分析
Eureka是怎么work的
那eureka client如何将本地服务的注册信息发送到远端的注册服务器eureka server上。通过下面的源码分析,看出Eureka Client的定时任务调用Eureka Server的Reset接口,而Eureka接收到调用请求后会处理服务的注册以及Eureka Server中的数据同步的问题。
服务注册
源码分析,看出服务注册可以认为是Eureka client自己完成,不需要服务本身来关心。
Eureka Client的定时任务调用Eureka Server的提供接口
在com.netflix.discovery.DiscoveryClient启动的时候,会初始化一个定时任务,定时的把本地的服务配置信息,即需要注册到远端的服务信息自动刷新到注册服务器上。
首先看一下Eureka的代码,在spring-cloud-netflix-eureka-server工程中可以找到这个依赖eureka-client-1.4.11.jar查看代码可以看到,
com.netflix.discovery.DiscoveryClient.java中的1240行可以看到Initializes all scheduled tasks,在1277行,可以看到InstanceInfoReplicator定时任务
在DiscoveryClient中初始化一个InstanceInfoReplicator,其实里面封装了以定时任务。
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
/**************************封装了定时任务**********************************/
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//点击可以查看start方法
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
com.netflix.discovery.DiscoveryClient中的 register()方法,大概在811行。
/**
* Register with the eureka service by making the appropriate REST call.
*/
boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
//Eureka Client客户端,调用Eureka服务端的入口
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
Eureka server服务端请求入口
ApplicationResource.java文件中第183行,如下所示,可以看出Eureka是通过http post的方式去服务注册
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
if(this.isBlank(info.getId())) {
return Response.status(400).entity("Missing instanceId").build();
} else if(this.isBlank(info.getHostName())) {
return Response.status(400).entity("Missing hostname").build();
} else if(this.isBlank(info.getAppName())) {
return Response.status(400).entity("Missing appName").build();
} else if(!this.appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build();
} else if(info.getDataCenterInfo() == null) {
return Response.status(400).entity("Missing dataCenterInfo").build();
} else if(info.getDataCenterInfo().getName() == null) {
return Response.status(400).entity("Missing dataCenterInfo Name").build();
} else {
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if(dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId();
if(this.isBlank(dataCenterInfoId)) {
boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
if(experimental) {
String amazonInfo1 = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
return Response.status(400).entity(amazonInfo1).build();
} if(dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;
String effectiveId = amazonInfo.get(MetaDataKey.instanceId);
if(effectiveId == null) {
amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
}
}
}
//进入到PeerAwareInstanceRegistryImpl中的register方法
this.registry.register(info, "true".equals(isReplication)); return Response.status(204).build();
}
}
InstanceRegistry.java文件中的52行,可以看到调用PeerAwareInstanceRegistryImpl中的278行register方法
public void register(InstanceInfo info, boolean isReplication) {
this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication);
}
PeerAwareInstanceRegistryImpl中的278行register方法
public void register(InstanceInfo info, boolean isReplication) {
int leaseDuration = 90;
if(info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
////调用父类方法注册
super.register(info, leaseDuration, isReplication);
// 同步Eureka中的服务信息
this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}
AbstractInstanceRegistry.java中151行,可以看到Eureka真正的服务注册实现的代码
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
this.read.lock();
Object gMap = (Map)this.registry.get(registrant.getAppName());
EurekaMonitors.REGISTER.increment(isReplication);
if(gMap == null) {
ConcurrentHashMap existingLease = new ConcurrentHashMap();
gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), existingLease);
if(gMap == null) {
gMap = existingLease;
}
}
Lease existingLease1 = (Lease)((Map)gMap).get(registrant.getId());
if(existingLease1 != null && existingLease1.getHolder() != null) {
Long lease1 = ((InstanceInfo)existingLease1.getHolder()).getLastDirtyTimestamp();
Long overriddenStatusFromMap = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", lease1, overriddenStatusFromMap);
if(lease1.longValue() > overriddenStatusFromMap.longValue()) {
logger.warn("There is an existing lease and the existing lease\'s dirty timestamp {} is greater than the one that is being registered {}", lease1, overriddenStatusFromMap);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = (InstanceInfo)existingLease1.getHolder();
}
} else {
Object lease = this.lock;
synchronized(this.lock) {
if(this.expectedNumberOfRenewsPerMin > 0) {
this.expectedNumberOfRenewsPerMin += 2;
this.numberOfRenewsPerMinThreshold = (int)((double)this.expectedNumberOfRenewsPerMin * this.serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease lease2 = new Lease(registrant, leaseDuration);
if(existingLease1 != null) {
lease2.setServiceUpTimestamp(existingLease1.getServiceUpTimestamp());
}
((Map)gMap).put(registrant.getId(), lease2);
AbstractInstanceRegistry.CircularQueue overriddenStatusFromMap1 = this.recentRegisteredQueue;
synchronized(this.recentRegisteredQueue) {
this.recentRegisteredQueue.add(new Pair(Long.valueOf(System.currentTimeMillis()), registrant.getAppName() + "(" + registrant.getId() + ")"));
}
if(!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the overrides", registrant.getOverriddenStatus(), registrant.getId());
if(!this.overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
this.overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap2 = (InstanceStatus)this.overriddenInstanceStatusMap.get(registrant.getId());
if(overriddenStatusFromMap2 != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap2);
registrant.setOverriddenStatus(overriddenStatusFromMap2);
}
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(registrant, existingLease1, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
if(InstanceStatus.UP.equals(registrant.getStatus())) {
lease2.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease2));
registrant.setLastUpdatedTimestamp();
this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), Boolean.valueOf(isReplication)});
} finally {
this.read.unlock();
}
}
说明:注册信息其实就是存储在一个 ConcurrentHashMap<string, map<string,="" lease<instanceinfo="">>> registry的结构中。
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap();
总结
ApplicationResource类接收Http服务请求,调用PeerAwareInstanceRegistryImpl的register方法,PeerAwareInstanceRegistryImpl完成服务注册后,
调用replicateToPeers向其它Eureka Server节点(Peer)做状态同步。如下图所示。
相关类
EurekaHttpClient:提供eureka 的客户端API方法 ,实现类JerseyReplicationClient ,eureka客户端发送http 请求类

ApplicationResource :eureka 服务端接收 eureka 客户端发送请求的处理类
InstanceRegistry eureka 服务端注册表实例类
LookupService::查询活动服务的实例接口
DefaultEurekaClientConfig:eureka 客户端默认配置类
EurekaClientConfigBean :eureka 客户端配置bean ,在配置文件中提示属性的类,配置前缀:eureka.client
Spring Cloud Eureka服务注册源码分析的更多相关文章
- Spring Cloud Eureka 服务注册中心(二)
序言 Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件 它主要包括两个组件:Eureka Server 和 Eureka Client Eureka Clie ...
- Spring Cloud Eureka 服务注册列表显示 IP 配置问题
服务提供者向 Eureka 注册中心注册,默认以 hostname 的形式显示,Eureka 服务页面显示的服务是机器名:端口,并不是IP+端口的形式 ,可以通过修改服务提供者配置自己的 IP 地址, ...
- spring cloud Eureka 服务注册发现与调用
记录一下用spring cloud Eureka搭建服务注册与发现框架的过程. 为了创建spring项目方便,使用了STS. 一.Eureka注册中心 1.新建项目-Spring Starter Pr ...
- SpringBoot + Spring Cloud Eureka 服务注册与发现
什么是Spring Cloud Eureka Eureka是Netflix公司开发的开源服务注册发现组件,服务发现可以说是微服务开发的核心功能了,微服务部署后一定要有服务注册和发现的能力,Eureka ...
- Spring Cloud学习 之 Spring Cloud Ribbon(负载均衡器源码分析)
文章目录 AbstractLoadBalancer: BaseLoadBalancer: DynamicServerListLoadBalancer: ServerList: ServerListUp ...
- Spring cloud实现服务注册及发现
服务注册与发现对于微服务系统来说非常重要.有了服务发现与注册,你就不需要整天改服务调用的配置文件了,你只需要使用服务的标识符,就可以访问到服务. 本文属于<7天学会spring cloud系列& ...
- Spring Cloud 之 服务注册与发现
作为微服务框架,提供服务注册发现是最基本的功能.Spring Cloud 针对服务注册发现 提供了 Eureka版本的实现 .Zookeeper版本的实现.Consul版本的实现.由于历史原因 Eur ...
- 如何优化Spring Cloud微服务注册中心架构?
作者: 石杉的架构笔记 1.再回顾:什么是服务注册中心? 先回顾一下什么叫做服务注册中心? 顾名思义,假设你有一个分布式系统,里面包含了多个服务,部署在不同的机器上,然后这些不同机器上的服务之间要互相 ...
- Spring Environment(二)源码分析
Spring Environment(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...
随机推荐
- vi/sed等遵循的搜索正则语法
转自:http://blog.csdn.net/lanxinju/article/details/5731843 一.查找 查找命令 /pattern<Enter> :向下查找patter ...
- phpcms开启在线编辑模版 方法
目录:\caches\configs\system.php 将:第20行 'tpl_edit'=> 0 修改为 'tpl_edit'=> 1 (0:默认的,不开启: 1: ...
- [计算机网络] C++模拟telnet登陆SMTP服务发送邮件过程
在百度文库中的<使用telnet协议收发邮件>,我们可以很清楚地看到如何通过telnet来进行发送邮件,下面是一些需要用到的命令,通过以下命令可以很容易实现邮件发送功能.为了更好地理解其中 ...
- ConcurrentHashMap弱一致性
[原文链接] 本文将用到Java内存模型的happens-before偏序关系(下文将简称为hb)以及ConcurrentHashMap的底层模型相关的知识.happens-before相关内容参见: ...
- .NET中SQL Server数据库连接方法
1. 使用本机上的SQL Server Express 实例上的用户实例. 用户实例的连接创建了一个新的SQL Server 实例.此连接只能是在本地SQL Server 2005实例上并 ...
- 【刷题】BZOJ 4827 [Hnoi2017]礼物
Description 我的室友最近喜欢上了一个可爱的小女生.马上就要到她的生日了,他决定买一对情侣手 环,一个留给自己,一个送给她.每个手环上各有 n 个装饰物,并且每个装饰物都有一定的亮度.但是在 ...
- [HAOI2007]分割矩阵 DP+推式子
发现最近好少写博客啊(其实是各种摆去了) 更一点吧 这道题要求最小化均方差,其实凭直觉来说就是要使每个块分的比较均匀一点,但是单单想到想到这些还是不够的, 首先f[i][j][k][l][t]表示以( ...
- Codeforces Round #466 (Div. 2) E. Cashback
Codeforces Round #466 (Div. 2) E. Cashback(dp + 贪心) 题意: 给一个长度为\(n\)的序列\(a_i\),给出一个整数\(c\) 定义序列中一段长度为 ...
- Spring多个数据源问题:DataSourceAutoConfiguration required a single bean, but * were found
原因: @EnableAutoConfiguration 这个注解会把配置文件号中的数据源全部都自动注入,不会默认注入一个,当使用其他数据源时再调用另外的数据源. 解决方法: 1.注释掉这个注解 2. ...
- webpack开发模式和生产模式设置及不同环境脚本执行
1. webpack设置开发模式和生产模式 (1). DefinePlugin插件设置 new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '& ...