Spring Cloud系列(四):Eureka原始碼解析之客戶端

toby.xu發表於2020-10-06

一、自動裝配

  1、根據自動裝配原理(詳見:Spring Boot系列(二):Spring Boot自動裝配原理解析),找到spring-cloud-netflix-eureka-client.jar的spring.factories,檢視spring.factories如下:

   2、進入EurekaClient的自動裝配類EurekaClientAutoConfiguration:

   3、@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)也就是說當容器中有EurekaDiscoveryClientCon figuration.Marker.class時,該配置類才起作用。接下來檢視spring.factories,發現還有一個配置類EurekaDiscoveryClientConfigura tion,該配置類就剛剛好往容器中匯入了EurekaDiscoveryClientConfiguration.Marker。

二、EurekaClient

  1、EurekaClientAutoConfiguration配置類

  1.1 匯入的核心Bean

  ① EurekaClientConfigBean:初始化eurekaClient配置;

  ② EurekaInstanceConfigBean:初始化eureka例項配置;

  ③ EurekaDiscoveryClient:初始化eureka發現客戶端;

  ④ EurekaServiceRegistry:初始化eureka服務註冊;

  ⑤ EurekaAutoServiceRegistration:初始化eureka自動服務註冊;

  ⑥ EurekaClient:初始化eureka客戶端;

  1.2 初始化EurekaClient

  ① 首先看下類的繼承圖:

   ② CloudEurekaClient構造方法:

  點進去會發現它呼叫父類的構造方法super(applicationInfoManager, config, args);最終來到如下方法:

@Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        //省略......
        this.applicationInfoManager = applicationInfoManager;
        //1、獲取要註冊的服務例項資訊
        InstanceInfo myInfo = applicationInfoManager.getInfo();
        //省略......
        instanceInfo = myInfo;
        //省略......
        //2、定義一些Executor
        try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
            //心跳Executor
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
            //本地快取重新整理Executor
            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
        /**
         * 1、初始化定時拉取服務註冊資訊和服務續約任務
         * 2、定義一個狀態變化監聽
         * 3、初始化定時服務註冊任務
         */
        initScheduledTasks();
        //省略......
    }

  ③ 進入initScheduledTasks()方法如下:

private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            //每隔30s執行CacheRefreshThread,重新整理本地快取
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new DiscoveryClient.CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            //每隔30s發一次心跳
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new DiscoveryClient.HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            //定時服務註冊
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize
            //狀態監聽器,當例項狀態改變時會呼叫監聽器的notify方法
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                //省略......
                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    //省略......
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                //往applicationInfoManager裡面註冊監聽器
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }
            //開啟定時服務註冊任務
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    } 

  上面方法核心邏輯:

  一:初始化定時拉取服務註冊資訊和服務續約任務;scheduler.schedule(TimedSupervisorTask),執行TimedSupervisorTask.ru n()方法,定時執行2個任務

  Ⅰ、CacheRefreshThread:定時重新整理本地註冊列表;

  Ⅱ、HeartbeatThread:定時向Eureka服務端傳送心跳,證明自己還活著;

  二:定義一個狀態變化監聽來監聽例項狀態的變化;

    statusChangeListener = new ApplicationInfoManager.StatusChangeListener(),裡面的核心方法notify(StatusChangeE vent statusChangeEvent),該方法裡面有個instanceInfoReplicator.onDemandUpdate()方法。然後把statusChangeListener監聽器往applicationInfoManager裡面註冊。當例項狀態改變時會呼叫監聽器的notify方法,也就是會呼叫instanceInfoReplicator.onDema ndUpdate()方法。

  三:初始化定時服務註冊任務;

  呼叫instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds())方法;進入到InstanceInfo Replicator.start(int initialDelayMs)方法如下:

public void start(int initialDelayMs) {
        if (started.compareAndSet(false, true)) {
            instanceInfo.setIsDirty();  // for initial register
            //延遲40s執行
            Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

  進入到InstanceInfoReplicator.run()方法如下:

public void run() {
        try {
            discoveryClient.refreshInstanceInfo();
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                //服務註冊,其實就是呼叫EurekaServer服務端服務註冊介面 httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            //每隔40s執行一次
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

  1.3 初始化EurekaAutoServiceRegistration

  ① EurekaAutoServiceRegistration類的繼承圖:

   實現了SmartLifecycle介面,會在EurekaAutoServiceRegistration初始化完成後,根據isAutoStartup為ture執行start方法。

  ② 進入EurekaAutoServiceRegistration.start()方法:

public void start() {
        //省略......
        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
            //服務註冊
            this.serviceRegistry.register(this.registration);
            //釋出InstanceRegisteredEvent事件
            this.context.publishEvent(new InstanceRegisteredEvent<>(this,
                    this.registration.getInstanceConfig()));
            //設定執行為true
            this.running.set(true);
        }
    }

  ③ 進入EurekaServiceRegistry.register()方法:

  該方法裡面reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());觸發狀態變化監聽器的notify方法,也就是呼叫到了ApplicationInfoManager.StatusChangeListener.notify方法,notify方法裡面執行instanceInfoRep licator.onDemandUpdate()方法,最終呼叫InstanceInfoReplicator.this.run()方法,進行服務註冊。

三、Eureka客戶端流程圖

  自此Eureka客戶端原始碼解析完成,Eureka服務端原始碼詳見:Spring Cloud系列(三):Eureka原始碼解析之服務端。Eureka應用詳見:Spring Cloud系列(二):Eureka應用詳

相關文章