【Spring Cloud】Eureka Client源码解析

【学习背景】

    在上一篇文章中,总结了Eureka中的相关组件与行为,也是分析其原理的第一步。本篇文章,先从Eureka Client源代码出发,了解其中的原理。

【学习内容】

    先看下图,分阶段地展示了Eureka Client需要完成的工作:

在这里插入图片描述

    在上图中,像
        1. 注册服务实例到Eureka Server中;
        2. 发送心跳更新与Eureka Server的租约;
        3. 在服务关闭时从Eureka Server中取消租约,服务下线;
        4. 查询在Eureka Server中注册的服务实例列表;

    这几个核心功能,都是通过DiscoveryClient完成的,DiscoveryClient类是Eureka Client的核心类,相关类图如下:
在这里插入图片描述
    在DiscoveryClient构造函数中,Eureka Client会执行从Eureka Server中拉取注册表信息、服务注册、初始化发送心跳、缓存刷新(重新拉取注册表信息)和按需注册定时任务等操作,可以说DiscoveryClient的构造函数贯穿了Eureka Client启动阶段的各项工作。其构造函数传入的参数如下:

//DiscoveryClient.java
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig
    config,  AbstractDiscoveryClientOptionalArgs  args,  Provider<BackupRegistry>
    backupRegistryProvider)

    ApplicationInfoManager是应用信息管理器,EurekaClientConfig是封装了Client与Server交互配置信息的类。

    AbstractDiscoveryClientOptionalArgs是用于注入一些可选参数,以及一些jersey1和jersey2通用的过滤器。

    BackupRegistry充当了备份注册中心的职责,当Eureka Client无法从任何一个Eureka Server中获取注册表信息时,BackupRegistry将被调用以获取注册表信息。默认的实现是NotImplementedRegistryImpl,即没有实现。

    接着定义一个基于线程池的定时器线程池ScheduledExecutorService,线程池大小为2,一个线程用于发送心跳,另一个线程用于缓存刷新,代码如下:

//DiscoveryClient.java
scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder()
            .setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
    heartbeatExecutor = new ThreadPoolExecutor(...);
    cacheRefreshExecutor = new ThreadPoolExecutor(...);

    之后,初始化Eureka Client与Eureka Server进行HTTP交互的Jersey客户端,再接着从Eureka Server中拉取注册表信息,代码如下:

//DiscoveryClient.java
private boolean fetchRegistry(boolean forceFullRegistryFetch){
    Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
    try {
        // 如果增量式拉取被禁止,或者Applications为null,进行全量拉取
        Applications applications = getApplications();
        if(clientConfig.shouldDisableDelta()
            ||(! Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
            || forceFullRegistryFetch
            ||(applications == null)
            ||(applications.getRegisteredApplications().size()== 0)
            ||(applications.getVersion()== -1))
        {
        ...
        // 全量拉取注册表信息
        getAndStoreFullRegistry();
    } else {
    // 增量拉取注册表信息
    getAndUpdateDelta(applications);
    }
        // 计算应用集合一致性哈希码
            applications.setAppsHashCode(applications.getReconcileHashCode());
            // 打印注册表上所有服务实例的总数量
            logTotalInstances();
    } catch(Throwable e){
        return false;
    } finally {
        if(tracer ! = null){
            tracer.stop();
        }
    }
    // 在更新远程实例状态之前推送缓存刷新事件,但是Eureka中并没有提供默认的事件监听器
    onCacheRefreshed();
    // 基于缓存中被刷新的数据更新远程实例状态
    updateInstanceRemoteStatus();
    // 注册表拉取成功,返回true
    return true;
}

    通过将Eureka Server中的注册表信息缓存到本地,就可以就近获取其他服务的相关信息,减少与Eureka Server的网络通信。一般来讲,在Eureka客户端,除了第一次拉取注册表信息(全量拉取),之后的信息拉取都会尝试只进行增量拉取。

    拉取完Eureka Server中的注册表信息后,将对服务实例进行注册,代码如下:

// DiscoveryClient.java
if(this.preRegistrationHandler ! = null){
     this.preRegistrationHandler.beforeRegistration();
 }
 if(clientConfig.shouldRegisterWithEureka() &&  clientConfig.shouldEnforceRegistr
     ationAtInit()){
     try {
         // 发起服务注册
         if(! register()){
             // 注册失败,抛出异常
             throw new IllegalStateException("Registration error at startup. Invalid
                 server response.");
         }
     } catch(Throwable th){
         throw new IllegalStateException(th);
     }
 }
     initScheduledTasks(); // 初始化定时任务

    服务注册后,紧接着是初始化定时任务,其中包含三个定时任务:一个用于向Eureka Server拉取注册表信息刷新本地缓存;一个用于向Eureka Server发送心跳;一个用户进行按需注册的操作。代码如下:

// DiscoveryClient.java
private void initScheduledTasks(){
    if(clientConfig.shouldFetchRegistry()){
        // 注册表缓存刷新定时器
        // 获取配置文件中刷新间隔,默认为30s,可以通过eureka.client.registry-fetch-
            interval-seconds进行设置
        int registryFetchIntervalSeconds  =  clientConfig.getRegistryFetchInterval
            Seconds();
        int expBackOffBound  =  clientConfig.getCacheRefreshExecutorExponentialBac
            kOffBound(); scheduler.schedule(
            new TimedSupervisorTask("cacheRefresh", scheduler, cacheRefreshExecutor,
                registryFetchIntervalSeconds,  TimeUnit.SECONDS,  expBackOffBound,
                    new CacheRefreshThread()
           ),
            registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }
    if(clientConfig.shouldRegisterWithEureka()){
        // 发送心跳定时器,默认30秒发送一次心跳
        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
        int expBackOffBound  =  clientConfig.getHeartbeatExecutorExponentialBackOff
            Bound();
        // 心跳定时器
        scheduler.schedule(
            new TimedSupervisorTask("heartbeat", scheduler, heartbeatExecutor,
                renewalIntervalInSecs,
                TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread()
           ),
            renewalIntervalInSecs, TimeUnit.SECONDS);
        // 按需注册定时器
        ...
}

    对于服务下线,一般情况下,应用服务在关闭的时候,Eureka Client会主动向Eureka Server注销自身在注册表中的信息。DiscoveryClient中对象销毁前执行的清理方法如下所示:

// DiscoveryClient.java
@PreDestroy
@Override
public synchronized void shutdown(){
    // 同步方法
    if(isShutdown.compareAndSet(false, true)){
        // 原子操作,确保只会执行一次
        if(statusChangeListener ! = null && applicationInfoManager ! = null){
        // 注销状态监听器
    applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
        }
        // 取消定时任务
        cancelScheduledTasks();
        if(applicationInfoManager ! = null && clientConfig.shouldRegisterWithEureka()){
            // 服务下线
            applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
            unregister();
        }
        // 关闭Jersy客户端
        if(eurekaTransport ! = null){
            eurekaTransport.shutdown();
        }
        // 关闭相关Monitor
        heartbeatStalenessMonitor.shutdown();
        registryStalenessMonitor.shutdown();
    }
}

服务下线方法代码如下:

void unregister(){
// It can be null if shouldRegisterWithEureka == false
if(eurekaTransport ! = null && eurekaTransport.registrationClient ! = null){
    try {
        EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.
            cancel(instanceInfo.getAppName(), instanceInfo.getId());
    } catch(Exception e){
    ...
    }
}
}

【学习总结】

    以上就是Eureka Client工作的核心内容及相关源代码。

    最后总结一下,主要依次做了以下的事情:

        1)相关配置的赋值,类似ApplicationInfoManager、EurekaClientConfig等。

        2)备份注册中心的初始化,默认没有实现。

        3)拉取Eureka Server注册表中的信息。

        4)注册前的预处理。

        5)向Eureka Server注册自身。

        6)初始化心跳定时任务、缓存刷新和按需注册等定时任务。

    这也是Eureka Client核心类DiscoveryClient构造函数中所做的事情。

猜你喜欢

转载自blog.csdn.net/u013034223/article/details/90136538