(源码) eureka server 接收服务注册过程解析

1. 源码定位

方法:com.netflix.discovery.InstanceInfoReplicator

源码版本: v1.7x

源码地址:https://github.com/Netflix/eureka/tree/v1.7.x


2.一切的开始InstanceInfoReplicator


2.1 线程任务InstanceInfoReplicator初始化与启动

eureka server想要接受请求,首先得有客户端发送。而在DiscoveryClient 初始化过程中创建了InstanceInfoReplicator 线程任务对象,并且在DiscoveryClient初始化的最后阶段开启了一个定时任务,在延迟40秒后开始注册服务到eureka server 上去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6fGNov8G-1621998188535)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/7fec88a2-7c6e-47b3-8a1d-e6e79776c2f5/Untitled.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0jpFQY3-1621998188538)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/5d3c0b0d-8116-447e-b117-63519cac8d9d/Untitled.png)]


2.2 开始向服务器注册

如果自身状态没有发生改变的话,其实是可以不用去注册的

public void run() {
        try {
            // 刷新自身状态信息和配置信息等,如果有发生变化,则InstanceInfo就会成为dirty状态
            discoveryClient.refreshInstanceInfo();
            // 如果自身信息发生变化,则存在dirtyTimestamp就是不为null
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                //
                discoveryClient.register();
                // 设置InstanceInfo的脏数据状态为false
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            // 30秒后自动执行下一次复制到对等节点的任务
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            // 把这个任务交给scheduledPeriodicRef,如果在下次执行onDemandUpdate时,会先判断上次的是否还在执行,如果还在执行,那就取消上次的执行
            scheduledPeriodicRef.set(next);
        }
    }

2.3 注册的具体内容

booleanregister()throwsThrowable {
logger.info(PREFIX+ appPathIdentifier + ": registering service...");
    EurekaHttpResponse<Void> httpResponse;
try{
//将自身信息注册到eureka server节点上
//这个eurekaTransport.registrationClient跟 eurekaTransport.queryClient的获取方式基本一
//致。只不过是两个独立的对象而已
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    }catch(Exception e) {
logger.warn("{} - registration failed {}",PREFIX+ appPathIdentifier, e.getMessage(), e);
throwe;
    }
if(logger.isInfoEnabled()) {
logger.info("{} - registration status: {}",PREFIX+ appPathIdentifier, httpResponse.getStatusCode());
    }
returnhttpResponse.getStatusCode() == 204;
}

关于httpResponse = eurekaTransport.registrationClient.register(instanceInfo); 这块的调用链路比较复杂,如果想了解的可以参考我写的 《eureka 巧妙的EurekaHttpClient工厂设计与请求发送过程》 这篇文章。

最终会发现调用时机上是JerseyApplicationClient内的register方法,这个方法实际上是在其父类之中com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register

在这里插入图片描述

3.基于/apps/{InstanceInfo.appName} 去找web处理类

这里是基于web框架jersey框架来实现的,这个框架国内比较小众,所以也不细看了。我们直接通过/apps 找到com.netflix.eureka.resources.ApplicationsResource

在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t41jAVA8-1621998188544)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8dbcf327-83a1-4f4e-a41a-66713d10bebc/Untitled.png)]

从上图可以看出,实际处理请求的应该是ApplicationResource



4.PeerAwareInstanceRegistryImpl 注册请求实际处理类

在这里插入图片描述

这里其实就是做了两件事:

  1. 将注册任务交给父类 super.register
  2. 将注册行为同步到eureka server的其他节点上去 replicateToPeers


5.注册过程源码解析

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        try {
            read.lock();
            // 本地缓存中获取续约列表
            Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
            REGISTER.increment(isReplication);
            if (gMap == null) {
                final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
                gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
            //获取本地已经存在的续约对象
            Lease<InstanceInfo> 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)) {
                Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);

                // 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.
                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");
                    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)
                        // 这里的计算也是按照心跳发送时间为30秒进行计算的(所以,不要瞎修改心跳发送周期,容易凉)
                        this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
                        this.numberOfRenewsPerMinThreshold =
                                (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
                    }
                }
                logger.debug("No previous lease information found; it is new registration");
            }
            Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
            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());
                    overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }
            InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }

            // Set the status based on the overridden status rules 这种服务实例状态基于状态覆写规则
            InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);

            // If the lease is registered with UP status, set lease service up timestamp
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                // 设置在线时间
                lease.serviceUp();
            }
            // 新增到增量队列里去
            registrant.setActionType(ActionType.ADDED);
            recentlyChangedQueue.add(new RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            // 读写缓存失效
            invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})",
                    registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
        } finally {
            read.unlock();
        }
    }

源码分析总结:

  1. 判断本地是否已经存在续约对象Lease,存在则将本地与传递过来的进行对比,用最近的那个。
  2. 如果是一个新的服务实例,则需要更新每分钟期待心跳数和每分钟最低应该有的心跳数(低于这个就会进入自我保护机制)。
  3. 加入服务实例ID和续约映射表中
  4. 加入最近注册队列
recentRegisteredQueue.add(newPair<Long, String>(
        System.currentTimeMillis(),
        registrant.getAppName() + "(" + registrant.getId() + ")"));
  1. 写入实例状态变更记录表中 overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());

  2. 服务上线标识更新 lease.serviceUp();

  3. 加入最近修改队列recentlyChangedQueue.add(new RecentlyChangedItem(lease));

  4. 读写缓存失效invalidateCache

这里可以看到注册就是在更新四个缓存:

  1. registry 是一个ConcurrentHashMap,键为appName,值也是一个map,是instanceId和Lease续约对象的映射表。 这个是保存服务实例和续约对象的容器
  2. recentRegisteredQueue 最近注册的InstanceInfo放入队列(主要用于debug和统计使用的)
  3. recentlyChangedQueue 最近修改的InstanceInfo放入队列 (主要用于增量注册表使用)
  4. overriddenInstanceStatusMap 服务实例id与服务状态的映射表

其实我们需要考虑一个问题,更新这四个缓存的目的是啥?有何意义?

  1. registry 在这里相当于eureka server的注册表,而我们在DiscoveryClient中的localRegionApps相当于Eureka Client的拉取的注册表缓存
  2. recentlyChangedQueue 主要是用于返回增量注册表给客户端
  3. overriddenInstanceStatusMap 这是在发送请求时,会将服务实例状态进行传递,直接从这里获取的缓存

缓存越多,维护起来越麻烦,但是相应的获取数据的方式会更加简单。



6.小结

eureka server接受服务注册其实就是更新本地缓存的注册表,然后加入最近更新队列来作为增量下发内容。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欢谷悠扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值