最新九(1),字节面试官

最后

如果觉得本文对你有帮助的话,不妨给我点个赞,关注一下吧!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

四.SpringCloud源码剖析-Eureka服务发现

五.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服务注册流程


在《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());

}

写在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源

本文已被CODING开源项目:【一线大厂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多线程学习手册)+(计算机网络顶级教程)等学习资源

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值