Spring Cloud源码分析之Eureka篇第六章:服务注册

在文章《Spring Cloud源码分析之Eureka篇第四章:服务注册是如何发起的 》的分析中,我们知道了作为Eureka Client的应用启动时,在com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法中,会做以下几件事:

  1. 周期性更新服务列表;
  2. 周期性服务续约;
  3. 服务注册逻辑;

本章学习的是服务注册逻辑的相关代码,对应用如何将自身信息注册到Eureka进行深入了解,原文地址:https://blog.csdn.net/boling_cavalry/article/details/82861618

概览

以下图片来自Netflix官方,图中显示Eureka Client会发起Register请求将自身注册到注册中心,这样其他Eureka client通过Get Registry请求就能获取到新注册应用的相关信息:
在这里插入图片描述

关于源码版本

本次分析的Spring Cloud版本为Edgware.RELEASE,对应的eureka-client版本为1.7.0;

源码分析

  1. 首先回顾com.netflix.discovery.DiscoveryClient类的initScheduledTasks方法,Eureka client在启动的时侯都会执行此方法,如下方所示,已经略去了周期性更新服务列表相关的代码:
//来自EurekaClientConfigBean,默认为true
if (clientConfig.shouldRegisterWithEureka()) {
			//续租间隔
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            //周期性任务处理超时后,下一次执行时将超时事件翻倍,但是不可超过expBackOffBound的设定范围
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
        <span class="token comment">//指定时间后启动周期性续租的任务</span>
        scheduler<span class="token punctuation">.</span><span class="token function">schedule</span><span class="token punctuation">(</span>
                <span class="token keyword">new</span> <span class="token class-name">TimedSupervisorTask</span><span class="token punctuation">(</span>
                        <span class="token string">"heartbeat"</span><span class="token punctuation">,</span>
                        scheduler<span class="token punctuation">,</span>
                        heartbeatExecutor<span class="token punctuation">,</span>
                        renewalIntervalInSecs<span class="token punctuation">,</span>
                        TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">,</span>
                        expBackOffBound<span class="token punctuation">,</span>
                        <span class="token keyword">new</span> <span class="token class-name">HeartbeatThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token punctuation">)</span><span class="token punctuation">,</span>
                renewalIntervalInSecs<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">//上报自身信息到Eureka server的操作委托给InstanceInfoReplicator实例发起,</span>
        <span class="token comment">//如果有多个场景需要上报,都由InstanceInfoReplicator进行调度和安排,</span>
        <span class="token comment">//并且还有限流逻辑,避免频繁先服务端请求</span>
        instanceInfoReplicator <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InstanceInfoReplicator</span><span class="token punctuation">(</span>
                <span class="token keyword">this</span><span class="token punctuation">,</span>
                instanceInfo<span class="token punctuation">,</span>
                clientConfig<span class="token punctuation">.</span><span class="token function">getInstanceInfoReplicationIntervalSeconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// burstSize</span>
		
		<span class="token comment">//监听和响应应用状态变化,包括从停止状态恢复或者进入停止状态,</span>
        statusChangeListener <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ApplicationInfoManager<span class="token punctuation">.</span>StatusChangeListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token annotation punctuation">@Override</span>
            <span class="token keyword">public</span> String <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">return</span> <span class="token string">"statusChangeListener"</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            <span class="token annotation punctuation">@Override</span>
            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">notify</span><span class="token punctuation">(</span>StatusChangeEvent statusChangeEvent<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>InstanceStatus<span class="token punctuation">.</span>DOWN <span class="token operator">==</span> statusChangeEvent<span class="token punctuation">.</span><span class="token function">getStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">||</span>
                        InstanceStatus<span class="token punctuation">.</span>DOWN <span class="token operator">==</span> statusChangeEvent<span class="token punctuation">.</span><span class="token function">getPreviousStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    <span class="token comment">// log at warn level if DOWN was involved</span>
                    logger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">"Saw local status change event {}"</span><span class="token punctuation">,</span> statusChangeEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
                    logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Saw local status change event {}"</span><span class="token punctuation">,</span> statusChangeEvent<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                <span class="token comment">//将自身状态上报都Eureka server(有限流逻辑避免频繁上报)</span>
                instanceInfoReplicator<span class="token punctuation">.</span><span class="token function">onDemandUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">;</span>

        <span class="token keyword">if</span> <span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">shouldOnDemandUpdateStatusChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        	<span class="token comment">//注册状态变化的监听</span>
            applicationInfoManager<span class="token punctuation">.</span><span class="token function">registerStatusChangeListener</span><span class="token punctuation">(</span>statusChangeListener<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
		<span class="token comment">//更新信息并注册到Eureka server</span>
        instanceInfoReplicator<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">getInitialInstanceInfoReplicationIntervalSeconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Not registering with Eureka server per configuration"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  1. 从上述代码可以看出,主动更新和状态变化触发的更新,都委托给成员变量instanceInfoReplicator执行,InstanceInfoReplicator是个辅助类,在服务注册过程中主要负责并发控制、周期性执行等工作,有关此类的详细介绍请参考文章《Eureka的InstanceInfoReplicator类(服务注册辅助工具)》
  2. 本文聚焦服务注册,因此InstanceInfoReplicator类本身的细节就不在此展开,这里主要关注的是InstanceInfoReplicator的run方法中注册到Eureka server的代码,如下图红框,discoveryClient.register()实现了注册的功能:
    在这里插入图片描述

注意:由上图绿框中代码可见,注册完成后又会提交一个一次性的延时任务,这就相当于周期性的执行run方法了,这么一来岂不是会周期性注册?其实并不会,红框上面是有个判断条件的:if (dirtyTimestamp != null),只要成员变量instanceInfo的isDirtyWithTime方法返回为空,就不会执行注册;

  1. 先看代码discoveryClient.refreshInstanceInfo(),弄清楚即将上报到Eureka server的信息是如何更新的,如下代码所示,信息更新的操作是委托给ApplicationInfoManager实例来完成的:
void refreshInstanceInfo() {
        //更新数据
        applicationInfoManager.refreshDataCenterInfoIfRequired();
        //如果续租时间有变化就要及时更新
        applicationInfoManager.refreshLeaseInfoIfRequired();
    InstanceStatus status<span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        status <span class="token operator">=</span> <span class="token function">getHealthCheckHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getStatus</span><span class="token punctuation">(</span>instanceInfo<span class="token punctuation">.</span><span class="token function">getStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        logger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">"Exception from healthcheckHandler.getStatus, setting status to DOWN"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//如果获取状态异常,就设置当前状态为DOWN</span>
        status <span class="token operator">=</span> InstanceStatus<span class="token punctuation">.</span>DOWN<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>null <span class="token operator">!=</span> status<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        applicationInfoManager<span class="token punctuation">.</span><span class="token function">setInstanceStatus</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  1. 接下来看看服务注册相关的代码,也就是DiscoveryClient类的register方法,如下所示,源码注释中说到是注册请求类型是Restful的,Eureka server的返回码如果是204表示注册成功,然而在前面的discoveryClient.register()方法内,其实并不关注这个返回值:
    /**
     * 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 {
            //注册操作
            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;
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 继续展开注册操作的源码eurekaTransport.registrationClient.register(instanceInfo),多层调用一路展开,最终由JerseyApplicationClient类来完成注册操作,对应源码在父类AbstractJerseyEurekaHttpClient中,如下所示,主要工作是利用jersey库的Restful Api将自身的信息POST到Eureka server:
@Override
    public EurekaHttpResponse<Void> register(InstanceInfo info) {
        String urlPath = "apps/" + info.getAppName();
        ClientResponse response = null;
        try {
            Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
            addExtraHeaders(resourceBuilder);
            response = resourceBuilder
                    .header("Accept-Encoding", "gzip")
                    .type(MediaType.APPLICATION_JSON_TYPE)
                    .accept(MediaType.APPLICATION_JSON)
                    .post(ClientResponse.class, info);
            return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                        response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                response.close();
            }
        }
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

至此,Eureka client向服务注册的源码就分析完毕了,过程相对简单,DiscoveryClient、InstanceInfoReplicator、ApplicationInfoManager、JerseyApplicationClient等实例各司其职将应用自身信息上报到Eureka server,由Eureka server保存,再被其他实例下载;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值