Spring Cloud源码分析之Eureka篇第五章:更新服务列表

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

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

本章学习的是周期性更新服务列表的相关代码,也就是定期获取所有注册到Eureka server上的应用的信息,原文地址:https://blog.csdn.net/boling_cavalry/article/details/82813180

概览

以下图片来自Netflix官方,图中显示Eureka Client会向注册中心发起Get Registry请求来获取服务列表,接下来就去看下对应的代码实现;
在这里插入图片描述

结论提前知晓

看源码易犯困,又难保持注意力集中,因此先抛结论吧,这样不看源码也有收获:

  1. Eureka client从注册中心更新服务列表,然后自身会做缓存;
  2. 作为服务消费者,就是从这些缓存信息中获取的服务提供者的信息;
  3. 增量更新的服务以30秒为周期循环调用;
  4. 增量更新数据在服务端保存时间为3分钟,因此Eureka client取得的数据虽然被称为"增量更新",仍然可能和30秒前取的数据一样,所以Eureka client要自己来处理重复信息;
  5. 由3、4两点可以推断出,Eureka client的增量更新,其实获取的是Eureka server最近三分钟内的变更,因此,如果Eureka client有超过三分钟没有做增量更新的话(例如网络问题),那么再调用增量更新接口时,那三分钟内Eureka server的变更就可能获取不到了,这就造成了Eureka server和Eureka client之间的数据不一致,需要有个方案来及时发现这个问题;
  6. 正常情况下,Eureka client多次增量更新后,最终的服务列表数据应该Eureka server保持一致,但如果期间发生异常,可能导致和Eureka server的数据不一致,为了暴露这个问题,Eureka server每次返回的增量更新数据中,会带有一致性哈希码,Eureka client用本地服务列表数据算出的一致性哈希码应该和Eureka server返回的一致,若不一致就证明增量更新出了问题导致Eureka client和Eureka server上的服务列表信息不一致了,此时需要全量更新;
  7. Eureka server上的服务列表信息对外提供JSON/XML两种格式下载;
  8. Eureka client使用jersey的SDK,去下载JSON格式的服务列表信息;

关于源码版本

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

如何做到周期性执行

更新服务列表和服务续约都是周期性循环执行的,这是如何实现的呢,来看initScheduledTasks方法的源码:
红框部分其实是一次性任务

如上图两个红框中所示,scheduler.schedule方法其实启动的是一个延时执行的一次性任务,不过TimedSupervisorTask内有乾坤,会在每次执行完任务后再启动一个同样的任务,这样就能实现周期性执行任务了,并且TimedSupervisorTask的功能还不止如此,它还负责任务超时、动态调节周期性间隔、线程池满、未知异常等各种情况的处理,推荐您参考《Eureka的TimedSupervisorTask类(自动调节间隔的周期性任务)》了解更多细节;

来自官方文档的指导信息

最准确的说明信息来自Netflix的官方文档,地址:https://github.com/Netflix/eureka/wiki/Understanding-eureka-client-server-communication#fetch-registry

学习源码之前先看文档可以确定大方向,不会因为陷入源码细节导致偏离学习目标,如下图所示:
在这里插入图片描述

对上文,我的理解:

  1. Eureka client从注册中心更新服务列表,然后自身会做缓存;
  2. 作为服务消费者,就是从这些缓存信息中获取的服务提供者的信息;
  3. 增量更新的服务以30秒为周期循环调用;
  4. 增量更新数据在服务端保存时间为3分钟,因此Eureka client取得的数据虽然被称为"增量更新",仍然可能和30秒前取的数据一样,所以Eureka client要自己来处理重复信息;
  5. 由3、4两点可以推断出,Eureka client的增量更新,其实获取的是Eureka server最近三分钟内的变更,因此,如果Eureka client有超过三分钟没有做增量更新的话(例如网络问题),那么再调用增量更新接口时,那三分钟内Eureka server的变更就可能获取不到了,这就造成了Eureka server和Eureka client之间的数据不一致,需要有个方案来及时发现这个问题;
  6. 正常情况下,Eureka client多次增量更新后,最终的服务列表数据应该Eureka server保持一致,但如果期间发生异常,可能导致和Eureka server的数据不一致,为了暴露这个问题,Eureka server每次返回的增量更新数据中,会带有一致性哈希码,Eureka client用本地服务列表数据算出的一致性哈希码应该和Eureka server返回的一致,若不一致就证明增量更新出了问题导致Eureka client和Eureka server上的服务列表信息不一致了,此时需要全量更新;
  7. Eureka server上的服务列表信息对外提供JSON/XML两种格式下载;
  8. Eureka client使用jersey的SDK,去下载JSON格式的服务列表信息;
    准备工作就到此,接下来学习源码,整个过程应围绕上述点八进行,不要过早陷入某些代码细节中;

源码分析

  1. 如下图红框所示,更新服务列表的逻辑已经封装在CacheRefreshThread类中:
    在这里插入图片描述

  2. CacheRefreshThread类中又是调用refreshRegistry方法来实现服务列表更新的,refreshRegistry方法如下:
    在这里插入图片描述

如上图所示,本文假设应用部署在非AWS环境,所以Eureka client不做region和zone相关的配置,因此上图绿框中的代码不会执行,我们聚焦红框中的代码,先看fetchRegistry方法;

  1. fetchRegistry方法源码如下,请注意中文注释:
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        //用Stopwatch做耗时分析
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 取出本地缓存的,之气获取的服务列表信息</span>
        Applications applications <span class="token operator">=</span> <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">//判断多个条件,确定是否触发全量更新,如下任一个满足都会全量更新:</span>
        <span class="token comment">//1. 是否禁用增量更新;</span>
        <span class="token comment">//2. 是否对某个region特别关注;</span>
        <span class="token comment">//3. 外部调用时是否通过入参指定全量更新;</span>
        <span class="token comment">//4. 本地还未缓存有效的服务列表信息;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">shouldDisableDelta</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
                <span class="token operator">||</span> <span class="token punctuation">(</span><span class="token operator">!</span>Strings<span class="token punctuation">.</span><span class="token function">isNullOrEmpty</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token operator">||</span> forceFullRegistryFetch
                <span class="token operator">||</span> <span class="token punctuation">(</span>applications <span class="token operator">==</span> null<span class="token punctuation">)</span>
                <span class="token operator">||</span> <span class="token punctuation">(</span>applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>
                <span class="token operator">||</span> <span class="token punctuation">(</span>applications<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">//Client application does not have latest library supporting delta</span>
        <span class="token punctuation">{</span>
        	<span class="token comment">//这些详细的日志可以看出触发全量更新的原因</span>
            logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Disable delta property : {}"</span><span class="token punctuation">,</span> clientConfig<span class="token punctuation">.</span><span class="token function">shouldDisableDelta</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</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">"Single vip registry refresh property : {}"</span><span class="token punctuation">,</span> clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</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">"Force full registry fetch : {}"</span><span class="token punctuation">,</span> forceFullRegistryFetch<span class="token punctuation">)</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">"Application is null : {}"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>applications <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">)</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">"Registered Applications size is zero : {}"</span><span class="token punctuation">,</span>
                    <span class="token punctuation">(</span>applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</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">"Application version is -1: {}"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>applications<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">//全量更新</span>
            <span class="token function">getAndStoreFullRegistry</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>
            <span class="token comment">//增量更新</span>
            <span class="token function">getAndUpdateDelta</span><span class="token punctuation">(</span>applications<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">//重新计算和设置一致性hash码</span>
        applications<span class="token punctuation">.</span><span class="token function">setAppsHashCode</span><span class="token punctuation">(</span>applications<span class="token punctuation">.</span><span class="token function">getReconcileHashCode</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>
        <span class="token function">logTotalInstances</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">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>PREFIX <span class="token operator">+</span> appPathIdentifier <span class="token operator">+</span> <span class="token string">" - was unable to refresh its cache! status = "</span> <span class="token operator">+</span> e<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>tracer <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            tracer<span class="token punctuation">.</span><span class="token function">stop</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 comment">//将本地缓存更新的事件广播给所有已注册的监听器,注意该方法已被CloudEurekaClient类重写</span>
    <span class="token function">onCacheRefreshed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">//检查刚刚更新的缓存中,有来自Eureka server的服务列表,其中包含了当前应用的状态,</span>
    <span class="token comment">//当前实例的成员变量lastRemoteInstanceStatus,记录的是最后一次更新的当前应用状态,</span>
    <span class="token comment">//上述两种状态在updateInstanceRemoteStatus方法中作比较 ,如果不一致,就更新lastRemoteInstanceStatus,并且广播对应的事件</span>
    <span class="token function">updateInstanceRemoteStatus</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 boolean">true</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
  • 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

上述代码中已有注释详细说明,就不另外赘述了,接下来细看getAndStoreFullRegistry和getAndUpdateDelta这两个方法,了解全量增量更新的细节;

全量更新本地缓存的服务列表

  1. getAndStoreFullRegistry方法负责全量更新,代码如下所示,非常简单的逻辑:
private void getAndStoreFullRegistry() throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();
    logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"Getting all instance registry info from the eureka server"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    Applications apps <span class="token operator">=</span> null<span class="token punctuation">;</span>
    <span class="token comment">//由于并没有配置特别关注的region信息,因此会调用eurekaTransport.queryClient.getApplications方法从服务端获取服务列表</span>
    EurekaHttpResponse<span class="token generics function"><span class="token punctuation">&lt;</span>Applications<span class="token punctuation">&gt;</span></span> httpResponse <span class="token operator">=</span> clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null
            <span class="token operator">?</span> eurekaTransport<span class="token punctuation">.</span>queryClient<span class="token punctuation">.</span><span class="token function">getApplications</span><span class="token punctuation">(</span>remoteRegionsRef<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token operator">:</span> eurekaTransport<span class="token punctuation">.</span>queryClient<span class="token punctuation">.</span><span class="token function">getVip</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> remoteRegionsRef<span class="token punctuation">.</span><span class="token function">get</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>httpResponse<span class="token punctuation">.</span><span class="token function">getStatusCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> Status<span class="token punctuation">.</span>OK<span class="token punctuation">.</span><span class="token function">getStatusCode</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>
        apps <span class="token operator">=</span> httpResponse<span class="token punctuation">.</span><span class="token function">getEntity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</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">"The response status is {}"</span><span class="token punctuation">,</span> httpResponse<span class="token punctuation">.</span><span class="token function">getStatusCode</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>apps <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"The application is null for some reason. Not storing this information"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> 
<span class="token comment">//考虑到多线程同步,只有CAS成功的线程,才会把自己从Eureka server获取的数据来替换本地缓存</span>
    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchRegistryGeneration<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>currentUpdateGeneration<span class="token punctuation">,</span> currentUpdateGeneration <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//localRegionApps就是本地缓存,是个AtomicReference实例</span>
        localRegionApps<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">filterAndShuffle</span><span class="token punctuation">(</span>apps<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Got full registry with apps hashcode {}"</span><span class="token punctuation">,</span> apps<span class="token punctuation">.</span><span class="token function">getAppsHashCode</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">warn</span><span class="token punctuation">(</span><span class="token string">"Not updating applications as another thread is updating it already"</span><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
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 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

  1. getAndStoreFullRegistry方法中并无复杂逻辑,只有eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())这段需要展开细看,和Eureka server交互的逻辑都在这里面,方法getApplications的具体实现是在EurekaHttpClientDecorator类:
@Override
    public EurekaHttpResponse<Applications> getApplications(final String... regions) {
        return execute(new RequestExecutor<Applications>() {
            @Override
            public EurekaHttpResponse<Applications> execute(EurekaHttpClient delegate) {
                return delegate.getApplications(regions);
            }
        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> RequestType <span class="token function">getRequestType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        	<span class="token comment">//本次向Eureka server请求的类型:获取服务列表</span>
            <span class="token keyword">return</span> RequestType<span class="token punctuation">.</span>GetApplications<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

EurekaHttpClientDecorator类从名字看是个装饰者模式的实现,看它的其他代码,发现各类远程服务都在此被封装成API了,例如注册的:

@Override
    public EurekaHttpResponse<Void> register(final InstanceInfo info) {
        return execute(new RequestExecutor<Void>() {
            @Override
            public EurekaHttpResponse<Void> execute(EurekaHttpClient delegate) {
                return delegate.register(info);
            }
        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> RequestType <span class="token function">getRequestType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> RequestType<span class="token punctuation">.</span>Register<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

还有续租的:

@Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(final String appName,
                                                          final String id,
                                                          final InstanceInfo info,
                                                          final InstanceStatus overriddenStatus) {
        return execute(new RequestExecutor<InstanceInfo>() {
            @Override
            public EurekaHttpResponse<InstanceInfo> execute(EurekaHttpClient delegate) {
                return delegate.sendHeartBeat(appName, id, info, overriddenStatus);
            }
        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> RequestType <span class="token function">getRequestType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> RequestType<span class="token punctuation">.</span>SendHeartBeat<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  1. 再继续追踪 delegate.register(info),进入了AbstractJerseyEurekaHttpClient类,这里面是各种网络请求的具体实现,EurekaHttpClientDecorator类中的getApplications、register、sendHeartBeat等方法对应的网络请求响应逻辑在AbstractJerseyEurekaHttpClient中都有具体实现,篇幅所限我们只关注getApplications:
@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
	//取全量数据的path是""apps"
	return getApplicationsInternal("apps/", regions);
}

@Override
public EurekaHttpResponse<Applications> getDelta(String... regions) {
//取增量数据的path是"“apps/delta”
return getApplicationsInternal(“apps/delta”, regions);
}

//具体的请求响应处理都在此方法中
private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath, String[] regions) {
ClientResponse response = null;
String regionsParamValue = null;
try {
//jersey、resource这些关键词都预示着这是个restful请求
WebResource webResource = jerseyClient.resource(serviceUrl).path(urlPath);
if (regions != null && regions.length > 0) {
regionsParamValue = StringUtil.join(regions);
webResource = webResource.queryParam(“regions”, regionsParamValue);
}
Builder requestBuilder = webResource.getRequestBuilder();
addExtraHeaders(requestBuilder);
//发起网络请求,将响应封装成ClientResponse实例
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);

        Applications applications <span class="token operator">=</span> null<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>response<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> Status<span class="token punctuation">.</span>OK<span class="token punctuation">.</span><span class="token function">getStatusCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> response<span class="token punctuation">.</span><span class="token function">hasEntity</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>
            applications <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token function">getEntity</span><span class="token punctuation">(</span>Applications<span class="token punctuation">.</span><span class="token keyword">class</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 function">anEurekaHttpResponse</span><span class="token punctuation">(</span>response<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> Applications<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">headers</span><span class="token punctuation">(</span><span class="token function">headersOf</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">entity</span><span class="token punctuation">(</span>applications<span class="token punctuation">)</span>
                <span class="token punctuation">.</span><span class="token function">build</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">finally</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isDebugEnabled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Jersey HTTP GET {}/{}?{}; statusCode={}"</span><span class="token punctuation">,</span>
                    serviceUrl<span class="token punctuation">,</span> urlPath<span class="token punctuation">,</span>
                    regionsParamValue <span class="token operator">==</span> null <span class="token operator">?</span> <span class="token string">""</span> <span class="token operator">:</span> <span class="token string">"regions="</span> <span class="token operator">+</span> regionsParamValue<span class="token punctuation">,</span>
                    response <span class="token operator">==</span> null <span class="token operator">?</span> <span class="token string">"N/A"</span> <span class="token operator">:</span> response<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">if</span> <span class="token punctuation">(</span>response <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            response<span class="token punctuation">.</span><span class="token function">close</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>

  • 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
  • 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

上述代码中,利用jersey-client库的API向Eureka server发起restful请求,并将响应数据封装到EurekaHttpResponse实例中返回;

小结:获取全量数据,是通过jersey-client库的API向Eureka server发起restful请求实现的,并将响应的服务列表数据放在一个成员变量中作为本地缓存;

获取服务列表信息的增量更新

获取服务列表信息的增量更新是通过getAndUpdateDelta方法完成的,具体分析请看下面的中文注释:

private void getAndUpdateDelta(Applications applications) throws Throwable {
        long currentUpdateGeneration = fetchRegistryGeneration.get();
    Applications delta <span class="token operator">=</span> null<span class="token punctuation">;</span>
    <span class="token comment">//增量信息是通过eurekaTransport.queryClient.getDelta方法完成的</span>
    EurekaHttpResponse<span class="token generics function"><span class="token punctuation">&lt;</span>Applications<span class="token punctuation">&gt;</span></span> httpResponse <span class="token operator">=</span> eurekaTransport<span class="token punctuation">.</span>queryClient<span class="token punctuation">.</span><span class="token function">getDelta</span><span class="token punctuation">(</span>remoteRegionsRef<span class="token punctuation">.</span><span class="token function">get</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>httpResponse<span class="token punctuation">.</span><span class="token function">getStatusCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> Status<span class="token punctuation">.</span>OK<span class="token punctuation">.</span><span class="token function">getStatusCode</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">//delta中保存了Eureka server返回的增量更新</span>
        delta <span class="token operator">=</span> httpResponse<span class="token punctuation">.</span><span class="token function">getEntity</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>delta <span class="token operator">==</span> null<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">"The server does not allow the delta revision to be applied because it is not safe. "</span>
                <span class="token operator">+</span> <span class="token string">"Hence got the full registry."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//如果增量信息为空,就直接发起一次全量更新</span>
        <span class="token function">getAndStoreFullRegistry</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">//考虑到多线程同步问题,这里通过CAS来确保请求发起到现在是线程安全的,</span>
    <span class="token comment">//如果这期间fetchRegistryGeneration变了,就表示其他线程也做了类似操作,因此放弃本次响应的数据</span>
    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchRegistryGeneration<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>currentUpdateGeneration<span class="token punctuation">,</span> currentUpdateGeneration <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Got delta update with apps hashcode {}"</span><span class="token punctuation">,</span> delta<span class="token punctuation">.</span><span class="token function">getAppsHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        String reconcileHashCode <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchRegistryUpdateLock<span class="token punctuation">.</span><span class="token function">tryLock</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">try</span> <span class="token punctuation">{</span>
                <span class="token comment">//用Eureka返回的增量数据和本地数据做合并操作,这个方法稍后会细说</span>
                <span class="token function">updateDelta</span><span class="token punctuation">(</span>delta<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">//用合并了增量数据之后的本地数据来生成一致性哈希码</span>
                reconcileHashCode <span class="token operator">=</span> <span class="token function">getReconcileHashCode</span><span class="token punctuation">(</span>applications<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
                fetchRegistryUpdateLock<span class="token punctuation">.</span><span class="token function">unlock</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">warn</span><span class="token punctuation">(</span><span class="token string">"Cannot acquire update lock, aborting getAndUpdateDelta"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">//Eureka server在返回增量更新数据时,也会返回服务端的一致性哈希码,</span>
        <span class="token comment">//理论上每次本地缓存数据经历了多次增量更新后,计算出的一致性哈希码应该是和服务端一致的,</span>
        <span class="token comment">//如果发现不一致,就证明本地缓存的服务列表信息和Eureka server不一致了,需要做一次全量更新</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>reconcileHashCode<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>delta<span class="token punctuation">.</span><span class="token function">getAppsHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">||</span> clientConfig<span class="token punctuation">.</span><span class="token function">shouldLogDeltaDiff</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">//一致性哈希码不同,就在reconcileAndLogDifference方法中做全量更新</span>
            <span class="token function">reconcileAndLogDifference</span><span class="token punctuation">(</span>delta<span class="token punctuation">,</span> reconcileHashCode<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment">// this makes a remoteCall</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">warn</span><span class="token punctuation">(</span><span class="token string">"Not updating application delta as another thread is updating it already"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Ignoring delta update with apps hashcode {}, as another thread is updating it already"</span><span class="token punctuation">,</span> delta<span class="token punctuation">.</span><span class="token function">getAppsHashCode</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>

  • 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
  • 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

上述代码中有几处需要注意:
a. 获取增量更新数据使用的方法是:eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
b. 将增量更新的数据和本地缓存合并的方法是: updateDelta(delta);
c. 通过检查一致性哈希码可以确定历经每一次增量更新后,本地的服务列表信息和Eureka server上的是否还保持一致,若不一致就要做一次全量更新,通过调用reconcileAndLogDifference方法来完成;

上述a、b、c三点,接下来依次展开:

  1. 向Eureka server发起网络请求的逻辑和前面全量更新的差不多,也是EurekaHttpClientDecorator和AbstractJerseyEurekaHttpClient这两个类合作实现的,先看EurekaHttpClientDecorator部分:
@Override
    public EurekaHttpResponse<Applications> getDelta(final String... regions) {
        return execute(new RequestExecutor<Applications>() {
            @Override
            public EurekaHttpResponse<Applications> execute(EurekaHttpClient delegate) {
                return delegate.getDelta(regions);
            }
        <span class="token annotation punctuation">@Override</span>
        <span class="token keyword">public</span> RequestType <span class="token function">getRequestType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> RequestType<span class="token punctuation">.</span>GetDelta<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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  1. 再看AbstractJerseyEurekaHttpClient类中的getDelta方法,居然和全量获取服务列表数据调用了相同的方法getApplicationsInternal,只是ur参数不一样而已;
    @Override
    public EurekaHttpResponse<Applications> getDelta(String... regions) {
        return getApplicationsInternal("apps/delta", regions);
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

由上述代码可见,从Eureka server的获取增量更新,和一些常见的方式略有区别:
a. 一般的增量更新是在请求中增加一个时间戳或者上次更新的tag号等参数,由服务端根据参数来判断哪些数据是客户端没有的;
b. 而这里的Eureka client却没有这类参数,联想到前面官方文档中提到的“Eureka会把更新数据保留三分钟”,就可以理解了:Eureka把最近的变更数据保留三分钟,这三分钟内每个Eureka client来请求增量更新是,server都返回同样的缓存数据,只要client能保证三分钟之内有一次请求,就能保证自己的数据和Eureka server端的保持一致;
c. 那么如果client有问题,导致超过三分钟才来获取增量更新数据,那就有可能client和server数据不一致了,此时就要有一种方式来判断是否不一致,如果不一致,client就会做一次全量更新,这种判断就是一致性哈希码;

  1. Eureka client获取到增量更新后,通过updateDelta方法将增量更新数据和本地数据做合并:
private void updateDelta(Applications delta) {
        int deltaCount = 0;
        //遍历所有服务
        for (Application app : delta.getRegisteredApplications()) {
            //遍历当前服务的所有实例	
            for (InstanceInfo instance : app.getInstances()) {
                //取出缓存的所有服务列表,用于合并
                Applications applications = getApplications();
                String instanceRegion = instanceRegionChecker.getInstanceRegion(instance);
                //判断正在处理的实例和当前应用是否在同一个region
                if (!instanceRegionChecker.isLocalRegion(instanceRegion)) {
                    //如果不是同一个region,接下来合并的数据就换成专门为其他region准备的缓存
                    Applications remoteApps = remoteRegionVsApps.get(instanceRegion);
                    if (null == remoteApps) {
                        remoteApps = new Applications();
                        remoteRegionVsApps.put(instanceRegion, remoteApps);
                    }
                    applications = remoteApps;
                }
            <span class="token operator">++</span>deltaCount<span class="token punctuation">;</span>
            
            <span class="token keyword">if</span> <span class="token punctuation">(</span>ActionType<span class="token punctuation">.</span>ADDED<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getActionType</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 comment">//对新增的实例的处理</span>
                Application existingApp <span class="token operator">=</span> applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</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>existingApp <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    applications<span class="token punctuation">.</span><span class="token function">addApplication</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Added instance {} to the existing apps in region {}"</span><span class="token punctuation">,</span> instance<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> instanceRegion<span class="token punctuation">)</span><span class="token punctuation">;</span>
                applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addInstance</span><span class="token punctuation">(</span>instance<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ActionType<span class="token punctuation">.</span>MODIFIED<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getActionType</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 comment">//对修改实例的处理</span>
                Application existingApp <span class="token operator">=</span> applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</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>existingApp <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    applications<span class="token punctuation">.</span><span class="token function">addApplication</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Modified instance {} to the existing apps "</span><span class="token punctuation">,</span> instance<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

                applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addInstance</span><span class="token punctuation">(</span>instance<span class="token punctuation">)</span><span class="token punctuation">;</span>

            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ActionType<span class="token punctuation">.</span>DELETED<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getActionType</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 comment">//对删除实例的处理</span>
                Application existingApp <span class="token operator">=</span> applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</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>existingApp <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    applications<span class="token punctuation">.</span><span class="token function">addApplication</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"Deleted instance {} to the existing apps "</span><span class="token punctuation">,</span> instance<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                applications<span class="token punctuation">.</span><span class="token function">getRegisteredApplications</span><span class="token punctuation">(</span>instance<span class="token punctuation">.</span><span class="token function">getAppName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">removeInstance</span><span class="token punctuation">(</span>instance<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>
    logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"The total number of instances fetched by the delta processor : {}"</span><span class="token punctuation">,</span> deltaCount<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setVersion</span><span class="token punctuation">(</span>delta<span class="token punctuation">.</span><span class="token function">getVersion</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>
    <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">shuffleInstances</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">shouldFilterOnlyUpInstances</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">//和当前应用不在同一个region的应用,其实例数据也要整理</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span>Applications applications <span class="token operator">:</span> remoteRegionVsApps<span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        applications<span class="token punctuation">.</span><span class="token function">setVersion</span><span class="token punctuation">(</span>delta<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        applications<span class="token punctuation">.</span><span class="token function">shuffleInstances</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">shouldFilterOnlyUpInstances</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>

  • 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
  • 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

上述代码有几点需要注意:
a. 检查每个服务的region,如果跨region的,就合并到另一个专门存放跨region服务的缓存中;
b. 增量数据中,对每个应用下实例的变动,分为新增、修改、删除三种,合并的过程就是对这三种数据在本地缓存中做不同的处理;
c. 合并过程中还会对缓存数据做整理,这样后续每次使用时,获取的多个实例其顺序是一样的;

  1. 前面曾经提到,如果Eureka client不及时做增量更新,那么有可能会错过Eureka server上的数据变化,导致两边的服务列表信息不一致,这个问题会通过一致性哈希码对比发现,发现后如何处理呢?先看增量更新的getAndUpdateDelta方法中的一个注释,如下图红框所示,个人觉得这个注释写得很好,内容既简洁又重要:
    在这里插入图片描述

上图红框中提醒:此处会发生一次远程调用,这说明发现Eureka server和Eureka client保存的服务列表数据不一致时会向Eureka server发起一次请求,打开reconcileAndLogDifference方法看详情:

private void reconcileAndLogDifference(Applications delta, String reconcileHashCode) throws Throwable {
        logger.debug("The Reconcile hashcodes do not match, client : {}, server : {}. Getting the full registry",
                reconcileHashCode, delta.getAppsHashCode());
    RECONCILE_HASH_CODES_MISMATCH<span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">long</span> currentUpdateGeneration <span class="token operator">=</span> fetchRegistryGeneration<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">//从Eureka server获取全量数据</span>
    EurekaHttpResponse<span class="token generics function"><span class="token punctuation">&lt;</span>Applications<span class="token punctuation">&gt;</span></span> httpResponse <span class="token operator">=</span> clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null
            <span class="token operator">?</span> eurekaTransport<span class="token punctuation">.</span>queryClient<span class="token punctuation">.</span><span class="token function">getApplications</span><span class="token punctuation">(</span>remoteRegionsRef<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
            <span class="token operator">:</span> eurekaTransport<span class="token punctuation">.</span>queryClient<span class="token punctuation">.</span><span class="token function">getVip</span><span class="token punctuation">(</span>clientConfig<span class="token punctuation">.</span><span class="token function">getRegistryRefreshSingleVipAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> remoteRegionsRef<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    Applications serverApps <span class="token operator">=</span> httpResponse<span class="token punctuation">.</span><span class="token function">getEntity</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>serverApps <span class="token operator">==</span> null<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">"Cannot fetch full registry from the server; reconciliation failure"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>logger<span class="token punctuation">.</span><span class="token function">isDebugEnabled</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">try</span> <span class="token punctuation">{</span>
            Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> List<span class="token generics function"><span class="token punctuation">&lt;</span>String<span class="token punctuation">&gt;</span></span><span class="token operator">&gt;</span> reconcileDiffMap <span class="token operator">=</span> <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getReconcileMapDiff</span><span class="token punctuation">(</span>serverApps<span class="token punctuation">)</span><span class="token punctuation">;</span>
            StringBuilder reconcileBuilder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword">for</span> <span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> List<span class="token generics function"><span class="token punctuation">&lt;</span>String<span class="token punctuation">&gt;</span></span><span class="token operator">&gt;</span> mapEntry <span class="token operator">:</span> reconcileDiffMap<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                reconcileBuilder<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>mapEntry<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">": "</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">for</span> <span class="token punctuation">(</span>String displayString <span class="token operator">:</span> mapEntry<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    reconcileBuilder<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>displayString<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                reconcileBuilder<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">'\n'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            String reconcileString <span class="token operator">=</span> reconcileBuilder<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"The reconcile string is {}"</span><span class="token punctuation">,</span> reconcileString<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">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Could not calculate reconcile string "</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token comment">//CAS成功就把全量数据更新到本地缓存中</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>fetchRegistryGeneration<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>currentUpdateGeneration<span class="token punctuation">,</span> currentUpdateGeneration <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        localRegionApps<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">filterAndShuffle</span><span class="token punctuation">(</span>serverApps<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setVersion</span><span class="token punctuation">(</span>delta<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        logger<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span>
                <span class="token string">"The Reconcile hashcodes after complete sync up, client : {}, server : {}."</span><span class="token punctuation">,</span>
                <span class="token function">getApplications</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getReconcileHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                delta<span class="token punctuation">.</span><span class="token function">getAppsHashCode</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">warn</span><span class="token punctuation">(</span><span class="token string">"Not setting the applications map as another thread has advanced the update generation"</span><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
  • 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
  • 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

上述代码较简单:从Eureka server获取全量数据,再尝试CAS,如果成功就更新本地缓存数据;

至此,全量和增量更新的源码都看过了,接下来看看更新完数据后的两次广播:更新缓存和状态变化(有变化才广播);

广播:更新缓存

更新缓存的广播是在onCacheRefreshed方法中执行的,该方法在扩展类CloudEurekaClient中被覆盖:

	@Override
	protected void onCacheRefreshed() {
		if (this.cacheRefreshedCount != null) {
			long newCount = this.cacheRefreshedCount.incrementAndGet();
			log.trace("onCacheRefreshed called with count: " + newCount);
			//spring容器内的广播
			this.publisher.publishEvent(new HeartbeatEvent(this, newCount));
		}
	}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码显示,这是个spring容器内的广播,this.publisher的类型是ApplicationEventPublisher,如果您对spring的广播机制有兴趣,可以参考文章《spring4.1.8扩展实战之三:广播与监听》

广播:本地状态变化

从Eureka server中取得的服务列表,自然也包括当前应用自己的信息,这个信息会保存在成员变量lastRemoteInstanceStatus中,每次更新了缓存后,都会用缓存中的信息和lastRemoteInstanceStatus对比,如果不一致,就表示在Eureka server端记录的当前应用状态发生了变化,此时就广播一次;

private synchronized void updateInstanceRemoteStatus() {
        // Determine this instance's status for this app and set to UNKNOWN if not found
        InstanceInfo.InstanceStatus currentRemoteInstanceStatus = null;
        if (instanceInfo.getAppName() != null) {
            Application app = getApplication(instanceInfo.getAppName());
            if (app != null) {
                InstanceInfo remoteInstanceInfo = app.getByInstanceId(instanceInfo.getId());
                if (remoteInstanceInfo != null) {
                    currentRemoteInstanceStatus = remoteInstanceInfo.getStatus();
                }
            }
        }
        if (currentRemoteInstanceStatus == null) {
            currentRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN;
        }
    <span class="token comment">// Notify if status changed</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>lastRemoteInstanceStatus <span class="token operator">!=</span> currentRemoteInstanceStatus<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">//这里发起广播</span>
        <span class="token function">onRemoteStatusChanged</span><span class="token punctuation">(</span>lastRemoteInstanceStatus<span class="token punctuation">,</span> currentRemoteInstanceStatus<span class="token punctuation">)</span><span class="token punctuation">;</span>
        lastRemoteInstanceStatus <span class="token operator">=</span> currentRemoteInstanceStatus<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
  • 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轮询请求会增加服务器压力,但使用增量更新再加上Server自身缓存3分钟数据的方式,可以有效的减少数据量和相关的计算,再加上一致性哈希码来弥补增量更新的弊端,在性能和完整性方面都有了保证,另外增量更新不需要client的时间戳,这样既节省性能又简化了实现逻辑,这种设计方式值得我们学习;

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-e44c3c0e64.css" rel="stylesheet">
                </div>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值