EurekaClient自動裝配及啓動流程解析

上篇文章中,咱們簡單介紹了EurekaServer自動裝配及啓動流程解析,本篇文章則繼續研究EurekaClient的相關代碼

老規矩,先看spring.factories文件,其中引入了一個配置類EurekaDiscoveryClientConfigServiceBootstrapConfigurationjava

@ConditionalOnClass(ConfigServicePropertySourceLocator.class)
@ConditionalOnProperty(value = "spring.cloud.config.discovery.enabled", matchIfMissing = false)
@Configuration
@Import({ EurekaDiscoveryClientConfiguration.class,
        EurekaClientAutoConfiguration.class })
public class EurekaDiscoveryClientConfigServiceBootstrapConfiguration {
}

上方兩個註解則是這個配置類是否可以開啓的條件,這裏就再也不展開,直接看它引入的配置類吧git

EurekaDiscoveryClientConfiguration

  1. 細心的讀者可能會發現這裏又註冊了一個Marker類,能夠猜想也是某個地方的開關
  2. EurekaClientConfigurationRefresher這個類看名字就知道這是當配置被動態刷新時的一個處理器,這裏也再也不展開了
  3. EurekaHealthCheckHandlerConfiguration這裏面註冊了一個Eureka健康檢查的處理類,這個健康檢查相關的原理分析能夠參考這篇文章:SpringBoot健康檢查實現原理

EurekaClientAutoConfiguration

這個類裏面全是重點,也是咱們本文的核心github

註解
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration"})

首先能夠看到這個類一共包含這些註解,咱們來一一解析比較重要的幾個註解吧spring

@Import(DiscoveryClientOptionalArgsConfiguration.class)

引入了兩個bean,RestTemplateDiscoveryClientOptionalArgsMutableDiscoveryClientOptionalArgs ,這兩個類的做用暫且不說bootstrap

@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)

剛纔說的Marker類的做用出來了app

@AutoConfigureBefore

既然必須在這三個類完成自動裝配以後才能進行裝配,那就表明着這三個類確定大有用途,研究一下吧ide

NoopDiscoveryClientAutoConfiguration

故名思意,負責服務發現的類,我們重點關注一下其中的幾個方法oop

  1. init
@PostConstruct
    public void init() {
        String host = "localhost";
        try {
            host = InetAddress.getLocalHost().getHostName();
        }
        catch (UnknownHostException e) {
            log.warn("Cannot get host info: (" + e.getMessage() + ")");
        }
        int port = findPort();
        this.serviceInstance = new DefaultServiceInstance(
                this.environment.getProperty("spring.application.name", "application"),
                host, port, false);
    }

這裏構造了一個DefaultServiceInstance對象,這個對象包含了當前項目的ip+端口+項目名稱fetch

  1. 注入beanNoopDiscoveryClient
@Bean
    public DiscoveryClient discoveryClient() {
        return new NoopDiscoveryClient(this.serviceInstance);
    }

再深刻看一下這個類ui

public class NoopDiscoveryClient implements DiscoveryClient {

    public NoopDiscoveryClient(ServiceInstance instance) {
    }

    @Override
    public String description() {
        return "Spring Cloud No-op DiscoveryClient";
    }

    @Override
    public List<ServiceInstance> getInstances(String serviceId) {
        return Collections.emptyList();
    }

    @Override
    public List<String> getServices() {
        return Collections.emptyList();
    }

}

這個類包含了獲取當前實例以及當前服務的方法,可是返回的都是空,那麼是否是會在後面的某個地方被覆蓋呢?

CommonsClientAutoConfiguration

進去深刻了解一下,哎喲,註冊了幾個bean:DiscoveryClientHealthIndicatorDiscoveryCompositeHealthIndicator。原來是健康檢查相關的東西,那就忽略了

ServiceRegistryAutoConfiguration

這個配置類中主要註冊了一個bean:ServiceRegistryEndpoint這個類主要是對外提供對與Eureka狀態的檢查

@ReadOperation
    public ResponseEntity getStatus() {
        if (this.registration == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("no registration found");
        }

        return ResponseEntity.ok().body(this.serviceRegistry.getStatus(this.registration));
    }

而Eureka的狀態則是經過serviceRegistry對象獲取的,這個對象會再下方詳細分析

註冊bean

接着來看這個類注入的幾個bean

EurekaClientConfigBean
@Bean
    @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
    public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
        EurekaClientConfigBean client = new EurekaClientConfigBean();
        if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
            client.setRegisterWithEureka(false);
        }
        return client;
    }

這個bean中包含了eureka.client.xxx系列的一些配置,詳細的配置信息能夠參考這裏:https://github.com/shiyujun/s...

EurekaInstanceConfigBean

這個bean中主要是包含eureka實例(eureka.instance.xxx系列)的一些配置信息,詳細的配置信息同上

RefreshableEurekaClientConfiguration.DiscoveryClient
@Bean(destroyMethod = "shutdown")
        @ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy
        public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
            manager.getInfo(); // force initialization
            return new CloudEurekaClient(manager, config, this.optionalArgs,
                    this.context);
        }

其中CloudEurekaClientDiscoveryClient的子類,而DiscoveryClient則是EurekaClient的核心類

public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
                             EurekaClientConfig config,
                             AbstractDiscoveryClientOptionalArgs<?> args,
                             ApplicationEventPublisher publisher) {
                             //這裏會調用父類DiscoveryClient的構造方法
        super(applicationInfoManager, config, args);
        this.applicationInfoManager = applicationInfoManager;
        this.publisher = publisher;
        this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
        ReflectionUtils.makeAccessible(this.eurekaTransportField);
    }

父類的構造方法中執行的代碼塊比較長,一些賦值操做等就忽略了,這裏只摘出比較重要的部分

  1. 初始化拉取監控和心跳監控
if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }
  1. 噹噹前實例不須要註冊到EurekaServer時,構造方法走到這裏就結束了
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);

            initTimestampMs = System.currentTimeMillis();
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                    initTimestampMs, this.getApplications().size());

            return;  
        }
  1. 初始化心跳線程和刷新線程以及它們的調度器
try {
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );
  1. 從EurekaServer拉取註冊信息
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
            fetchRegistryFromBackup();
        }

這裏fetchRegistry是第一次拉取註冊信息,若是拉取不成功的話則執行fetchRegistryFromBackup從備份註冊中心獲取,一樣,拉取的信息會放在以後的文章中

  1. 註冊以前的擴展點
if (this.preRegistrationHandler != null) {
            this.preRegistrationHandler.beforeRegistration();
        }

這裏是個空的實現,能夠經過實現PreRegistrationHandler接口作些什麼操做

  1. 向EurekaServer發起註冊
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

註冊方法爲register,一樣這裏先不展開

  1. 初始化幾個定時任務
initScheduledTasks();

private void initScheduledTasks() {
   // 從 EurekaServer 拉取註冊信息
   if (clientConfig.shouldFetchRegistry()) {
       int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
       int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
       scheduler.schedule(
               new TimedSupervisorTask(
                       "cacheRefresh",
                       scheduler,
                       cacheRefreshExecutor,
                       registryFetchIntervalSeconds,
                       TimeUnit.SECONDS,
                       expBackOffBound,
                       new CacheRefreshThread()
               ),
               registryFetchIntervalSeconds, TimeUnit.SECONDS);
   }

   // 向 EurekaServer 發送續租心跳
   if (clientConfig.shouldRegisterWithEureka()) {
       int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
       int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
       logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);

       scheduler.schedule(
               new TimedSupervisorTask(
                       "heartbeat",
                       scheduler,
                       heartbeatExecutor,
                       renewalIntervalInSecs,
                       TimeUnit.SECONDS,
                       expBackOffBound,
                       new HeartbeatThread()
               ),
               renewalIntervalInSecs, TimeUnit.SECONDS);

       instanceInfoReplicator = new InstanceInfoReplicator(
               this,
               instanceInfo,
               clientConfig.getInstanceInfoReplicationIntervalSeconds(),
               2); 

       statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
           @Override
           public String getId() {
               return "statusChangeListener";
           }

           @Override
           public void notify(StatusChangeEvent statusChangeEvent) {
               if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                       InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                   logger.warn("Saw local status change event {}", statusChangeEvent);
               } else {
                   logger.info("Saw local status change event {}", statusChangeEvent);
               }
               instanceInfoReplicator.onDemandUpdate();
           }
       };

       if (clientConfig.shouldOnDemandUpdateStatusChange()) {
       //註冊狀態監聽器
           applicationInfoManager.registerStatusChangeListener(statusChangeListener);
       }

       instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
   } else {
       logger.info("Not registering with Eureka server per configuration");
   }
}

至此,EurekaClient的自動裝配與啓動流程就解析完畢了
1

相關文章
相關標籤/搜索