最后
如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!
五.SpringCloud源码剖析-Eureka Client服务续约
六.SpringCloud源码剖析-Eureka Client取消注册
七.SpringCloud源码剖析-Eureka Server的自动配置
八.SpringCloud源码剖析-Eureka Server初始化流程
九.SpringCloud源码剖析-Eureka Server服务注册流程
十.SpringCloud源码剖析-Eureka Server服务续约
十一.SpringCloud源码剖析-Eureka Server服务注册表拉取
十二.SpringCloud源码剖析-Eureka Server服务剔除
十三.SpringCloud源码剖析-Eureka Server服务下线
本片文章的目的是分析Eureka Server的注册流程,您可以结合《Eureka Client服务注册》更容易理解
在《Eureka Server初始化流程》 文章中我们知道,在EurekaServerAutoConfiguration中注册了JerseyFilter用来处理所有的/eureka开头的请求,当Eureka Client客户端发起注册请求,请求被该Filter接待
/**
-
Register the Jersey filter
-
注册Jersey filter
*/
@Bean
public FilterRegistrationBean jerseyFilterRegistration(
javax.ws.rs.core.Application eurekaJerseyApp) {
FilterRegistrationBean bean = new FilterRegistrationBean();
//ServletContainer 是核心处理类
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
//处理的请求/eureka/**
bean.setUrlPatterns(
Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + “/*”));
return bean;
}
具体的实现是在ServletContainer中完成
//处理请求
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String requestURI, String servletPath, String queryString) throws IOException, ServletException {
Pattern p = this.getStaticContentPattern();
//处理 ContextPath 上下文路径
if (p != null && p.matcher(servletPath).matches()) {
chain.doFilter(request, response);
} else {
if (this.filterContextPath != null) {
if (!servletPath.startsWith(this.filterContextPath)) {
throw new ContainerException(“The servlet path, “” + servletPath + “”, does not start with the filter context path, “” + this.filterContextPath + “””);
}
if (servletPath.length() == this.filterContextPath.length()) {
if (this.webComponent.getResourceConfig().getFeature(“com.sun.jersey.config.feature.Redirect”)) {
URI l = UriBuilder.fromUri(request.getRequestURL().toString()).path(“/”).replaceQuery(queryString).build(new Object[0]);
response.setStatus(307);
response.setHeader(“Location”, l.toASCIIString());
return;
}
requestURI = requestURI + “/”;
}
}
UriBuilder absoluteUriBuilder = UriBuilder.fromUri(request.getRequestURL().toString());
URI baseUri = this.filterContextPath == null ? absoluteUriBuilder.replacePath(request.getContextPath()).path(“/”).build(new Object[0]) : absoluteUriBuilder.replacePath(request.getContextPath()).path(this.filterContextPath).path(“/”).build(new Object[0]);
URI requestUri = absoluteUriBuilder.replacePath(requestURI).replaceQuery(queryString).build(new Object[0]);
//这里调用service方法
int status = this.service(baseUri, requestUri, request, response);
if (this.forwardOn404 && status == 404 && !response.isCommitted()) {
response.setStatus(200);
chain.doFilter(request, response);
}
}
}
// 处理请求,调用WebComponent的service方法
public int service(URI baseUri, URI requestUri, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
return this.webComponent.service(baseUri, requestUri, request, response);
}
最终会调用ApplicationResource实现服务注册 , Eureka Server对于Eureka client注册服务实例,获取服务实例的的REST请求的都交给ApplicationResource处理,其中用来服务注册的方法是addInstance,我们来看一下他的源码
/**
服务实例注册,InstanceInfo是服务注册信息,isReplicationd为true代表是从其他Eureka Server节点复制实例,如果是isReplication为false,代表是Eureka Client 注册的
-
Registers information about a particular instance for an
-
{@link com.netflix.discovery.shared.Application}.
-
@param info
-
{@link InstanceInfo} information of the instance.
-
@param isReplication
-
a header parameter containing information whether this is
-
replicated from other nodes.
*/
@POST
@Consumes({“application/json”, “application/xml”})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
logger.debug(“Registering instance {} (replication={})”, info.getId(), isReplication);
//验证instanceinfo包含所有必需的必填字段
// validate that the instanceinfo contains all the necessary required fields
if (isBlank(info.getId())) {
return Response.status(400).entity(“Missing instanceId”).build();
} else if (isBlank(info.getHostName())) {
return Response.status(400).entity(“Missing hostname”).build();
} else if (isBlank(info.getIPAddr())) {
return Response.status(400).entity(“Missing ip address”).build();
} else if (isBlank(info.getAppName())) {
return Response.status(400).entity(“Missing appName”).build();
} else if (!appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + 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();
}
//处理客户端可能在数据缺失的情况下向错误的DataCenterInfo注册的情况
// handle cases where clients may be registering with bad DataCenterInfo with missing data
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if (dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
if (isBlank(dataCenterInfoId)) {
boolean experimental = “true”.equalsIgnoreCase(serverConfig.getExperimental(“registration.validation.dataCenterInfoId”));
if (experimental) {
String entity = “DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id”;
return Response.status(400).entity(entity).build();
} else if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
if (effectiveId == null) {
amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn(“Registering DataCenterInfo of type {} without an appropriate id”, dataCenterInfo.getClass());
}
}
}
//【重要】:这里在调用PeerAwareInstanceRegistry的register注册服务,使用的是实现类:InstanceRegistry
registry.register(info, “true”.equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
ApplicationResource.addInstance看方法名就能推测出他是用来注册实例的方法,其中参数InstanceInfo 是客户端提交的注册信息,请求头中isReplicationd为true代表是从其他Eureka Server节点复制实例,如果是isReplication为false,代表是Eureka Client 注册的
在做了一些列参数判断之后,这里在调用PeerAwareInstanceRegistry的register注册服务,使用的是实现类:InstanceRegistry,这个类在之前有介绍过,就是Eureak Server用来实现服务注册,服务发现,服务续约,取消注册等的具体实现,他的继承关系如下:
跟踪下去,InstanceRegistry .register方法源码如下
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {
public void register(final InstanceInfo info, final boolean isReplication) {
//调用handleRegistration方法,抛出事件:EurekaInstanceRegisteredEvent
this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
//调用父类PeerAwareInstanceRegistryImpl的register方法
super.register(info, isReplication);
}
//服务注册,抛出EurekaInstanceRegisteredEvent事件
private void handleRegistration(InstanceInfo info, int leaseDuration, boolean isReplication) {
this.log("register " + info.getAppName() + ", vip " + info.getVIPAddress() + ", leaseDuration " + leaseDuration + ", isReplication " + isReplication);
this.publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration, isReplication));
}
…省略…
}
这里在调用handleRegistration方法,抛出事件:EurekaInstanceRegisteredEvent后继续调用了super.register方法,即:PeerAwareInstanceRegistryImpl.register,继续跟踪下去:
@Singleton
public class PeerAwareInstanceRegistryImpl extends AbstractInstanceRegistry implements PeerAwareInstanceRegistry {
/**
注册服务信息 InstanceInfo,并将此信息InstanceInfo复制到所有对等的eureka server节点。
如果这是来自其他副本节点的复制事件,则不会复制它。
-
Registers the information about the {@link InstanceInfo} and replicates
-
this information to all peer eureka nodes. If this is replication event
-
from other replica nodes then it is not replicated.
-
@param info
-
the {@link InstanceInfo} to be registered and replicated.
-
@param isReplication
-
true if this is a replication event from other replica nodes,
-
false otherwise.
*/
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
//租期失效时间 90 s
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
//如果服务的租期失效时间大于默认的90s,则重新赋值租期时间
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//服务注册
super.register(info, leaseDuration, isReplication);
//把注册的服务信息复制到其他的Eureka Server 节点,注意这里是Action.Register
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
}
该方法做了3个事情,
-
1是更新租期失效时间,
-
2是调用super.register服务注册(AbstractInstanceRegistry.register)
-
3是调用replicateToPeers把服务实例拷贝到其他的Eureak Server节点
我们先看下super.register方法即:AbstractInstanceRegistry.register方法的源码
/**
-
Registers a new instance with a given duration.
-
@see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
*/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
//获取锁:ReentrantReadWriteLock.lock()
read.lock();
//根据注册的服务的名字取本地服务注册表中获取服务注册信息,如果该服务已经被注册了,那么registry中将会存在它
Map<String, Lease> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
//如果该服务实例没被注册,就把服务实例注册到本地的registry中,本质是一个Map
final ConcurrentHashMap<String, Lease> gNewMap = new ConcurrentHashMap<String, Lease>();
//没有的话就添加到registry中
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
//根据服务实例id获取服务的租约对象
Lease existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
//如果已经有租约,则保留最后的脏时间戳而不覆盖它
if (existingLease != null && (existingLease.getHolder() != null)) {
//registry中已经存在的当前服务的最后修改时间的时间戳
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
//提交注册的当前服务的最后修改时间
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug(“Existing lease found (existing={}, provided={}”, existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
//因为如果时间戳相等,我们仍然采用远程传输的InstanceInfo而不是服务器本地副本
// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
// InstanceInfo instead of the server local copy.
//如果已存在的该服务的修改时间 大于 当前提交注册的该服务的最后修改时间,
//则采用registy中已存在的服务为准,因为要选择修改时间靠后的
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn(“There is an existing lease and the existing lease’s dirty timestamp {} is greater” +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn(“Using the existing instanceInfo instead of the new instanceInfo as the registrant”);
//使用现有的instanceInfo代替新的instanceInfo作为注册者
registrant = existingLease.getHolder();
}
} else {
//执行到这里,说明该服务是新注册
// The lease does not exist and hence it is a new registration
synchronized (lock) {
//这里在计算服务的续约频率值
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
// (1
// for 30 seconds, 2 for a minute)
//(expectedNumberOfRenewsPerMin)期待的每分钟续订次数,默认是30s/个,给他增加到2,每分钟2个请求
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
//修改numberOfRenewsPerMinThreshold每分钟续约阀值 = 2 *(85%),
//RenewalPercentThreshold是获取续订阈值百分比
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
logger.debug(“No previous lease information found; it is new registration”);
}
//创建租约对象,把注册实例和租期放进去
Lease lease = new Lease(registrant, leaseDuration);
if (existingLease != null) {
//设置服务上线时间
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//以注册的实例的ID为key把服务实例存封装到 Map
gMap.put(registrant.getId(), lease);
//添加到注册队列
synchronized (recentRegisteredQueue) {
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + “(” + registrant.getId() + “)”));
}
// This is where the initial state transfer of overridden status happens
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 (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info(“Not found overridden id {} and hence adding it”, registrant.getId());
//添加服务的OverriddenStatus
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
写在最后
可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。
上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
info(“Not found overridden id {} and hence adding it”, registrant.getId());
//添加服务的OverriddenStatus
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
写在最后
可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。
[外链图片转存中…(img-kFpDm59B-1715659453045)]
[外链图片转存中…(img-FqcaC1eA-1715659453045)]
上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源