spring-cloud-eureka (一) 原理分析

spring-cloud-eureka是spring-cloud-nettfix微服務套件中的一部分, 它基於nettfix-eureka做了二次封裝,主要負責微服務架構中的服務治理功能。
如果瞭解dubbo的朋友應該知道,dubbo就是一個服務治理的框架,dubbo是基於zookeeper實現服務治理功能的。至於dubbo和nettfix-eureka的區別這裏不多說,網上有不少這類文章。

服務治理一般都會有兩個功能:服務註冊、服務發現。通常會有一個註冊中心,每個服務單元向註冊中心登記自己信息,比如提供的服務,ip, 端口以及一些附近加信息等。註冊中心會將新的服務實例發送給其它依賴此服務的實例。
服務治理機制

服務註冊

服務提供者在啓動時會將自己的信息註冊到Eureka Server, Eureka Server收到信息後, 會將數據信息存儲在一個雙層結構的Map中,其中第一層的key是服務名,第二層的key是具體服務的實例名。

服務同步

如果有多個Eureka Server,一個服務提供者向其中一個Eureka Server註冊了,這個Eureka Server會向集羣內的其它Eureka Server轉發這個服務提供者的註冊信息,從而實現實現Eureka Server之間的服務同步。

服務續約

在註冊完成成後,服務提供者會維護一個心中持續發送信息給Eureka Server(註冊中心)表示正常運行,以防止Eureka Server將該服務實例從服務列表中剔除。

服務下線

當服務實例正常關閉時,它會發送一個服務下線的消息給註冊中心,註冊中心收到信息後,會將該服務實例狀態置爲下線,並把該信息傳播出去。

獲取服務

當一個服務實例依賴另一個服務時,這時這個服務實例又充當了服務消費者,它會發送一個信息給註冊中心, 請求獲取註冊的服務清單,註冊中心會維護一份只讀的服務清單來返回給服務消費者。

失效剔除

有時候,服務實例可能無法正常提供服務,而註冊中心沒有收到服務下線的信息。註冊中心會創建一個定時任務,將超過一定時間沒有服務續約消息的服務實例從服務清單中剔除。

自我保護

上面講到失效剔除時,會將超過一定時間沒有收到服務續約消息的實例從服務清單中剔除掉,在這中間還有一個邏輯。如果在運行期間,統計心跳成功的比例低於85%(心跳閾值),註冊中心會將當前服務清單中的實例註冊信息保護起來,讓這些實例不會過期。但是在這種情況下,若服務實例出現問題,那麼服務消費者可能會拿到實際已經不能正常運行的服務實例,就會出現調用失敗的情況,所以客戶端需要有容錯機制,比如請求重試,或斷路器等。

但是有一個定時任務默認每15分鐘執行一次,會根據運行狀況重新計算心跳閾值;但也可能不重新計算,這時,Eureka Server的自我保護狀態會一直存在。

如果要關閉自我保護機制,可以將eureka.server.enable-self-preservation設置爲false,以確保註冊中心將不可用的服務實例及時剔除。


源碼分析

Eureka分爲註冊中心,也就是Eureka-Server,或者稱服務端,註冊到服務端的服務實例稱爲客戶端,客戶端又抽象成服務提供者,服務消費者。其中在Eureka中,對於一客戶端既是服務提供者,同時也可能是服務消費者。服務端也會向另一個服務端實例註冊自己的信息,從而實現Server集羣。


Eureka Client啓動過程

首先是啓動服務,需要添加@EnableDiscoveryClient註解,這樣在啓動時纔會去加載運行Eureka相關的代碼。 查看@EnableDiscoveryClient的源碼,其中@Import(EnableDiscoveryClientImportSelector.class) 指向了org.springframework.cloud.client.discovery.EnableDiscoveryClientImportSelector類, 該類繼承了org.springframework.cloud.commons.util.SpringFactoryImportSelector,所以會執行spring.factories中key爲org.springframework.cloud.client.discovery.EnableDiscoveryClient的@Configuration類

代碼示例如下:

package org.springframework.cloud.client.discovery;

...

/**
 * @author Spencer Gibb
 */
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
        extends SpringFactoryImportSelector<EnableDiscoveryClient> {
        ... 
}

spring-cloud-netflix-eureka-client-{version}.jar包下spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration

org.springframework.cloud.client.discovery.EnableDiscoveryClient=\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration

從中可以看出會先去執行EurekaDiscoveryClientConfiguration配置類,加載其中的配置,然後再去執行EurekaClientAutoConfiguration配置類。兩個配置類的代碼如下:

EurekaDiscoveryClientConfiguration

/*
 * Copyright 2013-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.eureka;

...
import org.springframework.boot.actuate.health.HealthAggregator;
import org.springframework.boot.actuate.health.OrderedHealthAggregator;
...
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
...
import org.springframework.context.event.EventListener;

import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;

import lombok.extern.apachecommons.CommonsLog;

/**
 * @author Dave Syer
 * @author Spencer Gibb
 * @author Jon Schneider
 * @author Jakub Narloch
 */
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@CommonsLog
public class EurekaDiscoveryClientConfiguration {

    class Marker {}

    //Eureka discover client(eureka 客戶端) 標記
    @Bean
    public Marker eurekaDiscoverClientMarker() {
        return new Marker();
    }

    @Configuration
    @ConditionalOnClass(RefreshScopeRefreshedEvent.class)
    protected static class EurekaClientConfigurationRefresher {

        @Autowired(required = false)
        private EurekaAutoServiceRegistration autoRegistration;

        @EventListener(RefreshScopeRefreshedEvent.class)
        public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
            if (autoRegistration != null) {
                // register in case meta data changed
                this.autoRegistration.stop();
                this.autoRegistration.start();
            }
        }
    }

    @Configuration
    @ConditionalOnClass(Endpoint.class)
    protected static class EurekaHealthIndicatorConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public EurekaHealthIndicator eurekaHealthIndicator(EurekaClient eurekaClient,
                EurekaInstanceConfig instanceConfig, EurekaClientConfig clientConfig) {
            return new EurekaHealthIndicator(eurekaClient, instanceConfig, clientConfig);
        }
    }

    @Configuration
    @ConditionalOnProperty(value = "eureka.client.healthcheck.enabled", matchIfMissing = false)
    protected static class EurekaHealthCheckHandlerConfiguration {

        @Autowired(required = false)
        private HealthAggregator healthAggregator = new OrderedHealthAggregator();

        @Bean
        @ConditionalOnMissingBean(HealthCheckHandler.class)
        public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
            return new EurekaHealthCheckHandler(this.healthAggregator);
        }
    }
}

EurekaClientAutoConfiguration

package org.springframework.cloud.netflix.eureka;

...

/**
 * @author Dave Syer
 * @author Spencer Gibb
 * @author Jon Schneider
 * @author Matt Jenkins
 * @author Ryan Baxter
 */
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
//在EurekaDiscoveryClientConfiguration類中創建發佈的標記類
@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")
public class EurekaClientAutoConfiguration {
    ...
    /**
     *在spring-cloud-context-{version}.jar!META-INF/spring.factories中有一段:
     org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\
org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\
org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration

其中包含了RefreshAutoConfiguration配置類,所以會通過下面這個配置類創建EurekaClient
*/
    @Configuration
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired(required = false)
        private DiscoveryClientOptionalArgs optionalArgs;



        //創建EurekaClient
        @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);
        }

        @Bean
        @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy
        public ApplicationInfoManager eurekaApplicationInfoManager(
                EurekaInstanceConfig config) {
            InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
            return new ApplicationInfoManager(config, instanceInfo);
        }

    }
    ...
}

以上是@EnableDiscoveryClient的啓動邏輯,從EnableDiscoveryClient的註釋中我們可以看到,它主要是用來開啓DiscoveryClient的實例。而搜索DiscoveryClient發現有一個類和一個接口,其中org.springframework.cloud.client.discovery.DiscoveryClient是spring cloud的接口,它定義發現服務的常用抽象方法,通過該接口可以有效的屏蔽服務治理的實現細節,所以spring cloud構建微服務應用可以方便的切換不同的服務治理框架。org.springframework.cloud.netflix.eureka.EurekaDiscoveryClient是對該接口的實現,它對Eureka服務發現進行了封裝,依賴Netfix Eureka的com.netflix.discovery.EurekaClient接口,EurekaClient接口繼承了LookupService接口,這兩個接口都是Netflix Eureka開源包中的內容,定義了Eureka服務發現的抽象方法,其實現類是com.netflix.discovery.DiscoveryClient類。

從該類的註釋可以看出,該類包含服務註冊、服務續約、服務下線、獲取服務等功能。

根據上面的代碼,可以知道EurekaClient是在EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration#eurekaClient(ApplicationInfoManager, EurekaClientConfig, EurekaInstanceConfig)方法中創建的,創建的是org.springframework.cloud.netflix.eureka.CloudEurekaClient。

EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration

    @Configuration
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired(required = false)
        private DiscoveryClientOptionalArgs optionalArgs;

        @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);
        }
        ...
    }

CloudEurekaClient

package org.springframework.cloud.netflix.eureka;

...

public class CloudEurekaClient extends DiscoveryClient {
    ...
    public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
                             EurekaClientConfig config,
                             DiscoveryClientOptionalArgs args,
                             ApplicationEventPublisher publisher) {
        super(applicationInfoManager, config, args);
        this.applicationInfoManager = applicationInfoManager;
        this.publisher = publisher;
        this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
        ReflectionUtils.makeAccessible(this.eurekaTransportField);
    }
    ...
}

可以看到,CloudEurekaClient繼承了com.netflix.discovery.DiscoveryClient,在構造方法中調用了initScheduledTasks()方法,在該方法中根據配置中的eureka.client.fetch-registry和eureka.client.register-with-eureka的值,判斷是否創建拉取服務清單的定時任務和服務續約的定時任務以及服務註冊的定時任務。

CloudEurekaClient

...
public class DiscoveryClient implements EurekaClient {
    ...
    /**
     * Initializes all scheduled tasks.
     */
    private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            //創建拉取服務清單的定時任務
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new CacheRefreshThread()
                    ),
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);

            //創建服務續約的定時任務(心跳)
            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            //這是一個Runnable接口實現類,服務註冊的邏輯就在run()方法中。
            // InstanceInfo replicator
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize
            //服務實例狀態監聽器
            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()) {
                        // log at warn level if DOWN was involved
                        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");
        }
    }
    ...

}

InstanceInfoReplicator

class InstanceInfoReplicator implements Runnable {
    ...
    public void start(int initialDelayMs) {
        if (started.compareAndSet(false, true)) {
            instanceInfo.setIsDirty();  // for initial register
            //將註冊的動作添加到定時任務中
            Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }

    public void stop() {
        scheduler.shutdownNow();
        started.set(false);
    }

    public boolean onDemandUpdate() {
        if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
            scheduler.submit(new Runnable() {
                @Override
                public void run() {
                    logger.debug("Executing on-demand update of local InstanceInfo");

                    Future latestPeriodic = scheduledPeriodicRef.get();
                    if (latestPeriodic != null && !latestPeriodic.isDone()) {
                        logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
                        latestPeriodic.cancel(false);
                    }

                    InstanceInfoReplicator.this.run();
                }
            });
            return true;
        } else {
            logger.warn("Ignoring onDemand update due to rate limiter");
            return false;
        }
    }

    public void run() {
        try {
            discoveryClient.refreshInstanceInfo();

            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
                //註冊到註冊中心,並標記
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
}

從上述的代碼中可以到看到,服務在啓動時,根據配置創建了相關的定時任務,如服務註冊、服務獲取、服務續約,但是這個功能的邏輯入口依然是在com.netflix.discovery.DiscoveryClient中:

package com.netflix.discovery;
...
@Singleton
public class DiscoveryClient implements EurekaClient {
    ...
    /**
     * Register with the eureka service by making the appropriate REST call.
     */
     //註冊
    boolean register() throws Throwable {
        logger.info(PREFIX + appPathIdentifier + ": registering service...");
        EurekaHttpResponse<Void> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        } catch (Exception e) {
            logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
        }
        return httpResponse.getStatusCode() == 204;
    }

    /**
     * Renew with the eureka service by making the appropriate REST call
     */
     //續約
    boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
            if (httpResponse.getStatusCode() == 404) {
                REREGISTER_COUNTER.increment();
                logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
                return register();
            }
            return httpResponse.getStatusCode() == 200;
        } catch (Throwable e) {
            logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
            return false;
        }
    }
    ...
    //下線
    @PreDestroy
    @Override
    public synchronized void shutdown() {
        if (isShutdown.compareAndSet(false, true)) {
            logger.info("Shutting down DiscoveryClient ...");

            if (statusChangeListener != null && applicationInfoManager != null) {
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }

            cancelScheduledTasks();

            // If APPINFO was registered
            if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka()) {
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                unregister();
            }

            if (eurekaTransport != null) {
                eurekaTransport.shutdown();
            }

            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();

            logger.info("Completed shut down of DiscoveryClient");
        }
    }
    ...
    //獲取服務
    private boolean fetchRegistry(boolean forceFullRegistryFetch) {
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
            // If the delta is disabled or if it is the first time, get all
            // applications
            Applications applications = getApplications();

            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
                logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
                logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
                logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
                logger.info("Application is null : {}", (applications == null));
                logger.info("Registered Applications size is zero : {}",
                        (applications.getRegisteredApplications().size() == 0));
                logger.info("Application version is -1: {}", (applications.getVersion() == -1));
                getAndStoreFullRegistry();
            } else {
                getAndUpdateDelta(applications);
            }
            applications.setAppsHashCode(applications.getReconcileHashCode());
            logTotalInstances();
        } catch (Throwable e) {
            logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e);
            return false;
        } finally {
            if (tracer != null) {
                tracer.stop();
            }
        }

        // Notify about cache refresh before updating the instance remote status
        onCacheRefreshed();

        // Update remote status based on refreshed data held in the cache
        updateInstanceRemoteStatus();

        // registry was fetched successfully, so return true
        return true;
    }
    ...

    void refreshRegistry() {
        try {
            ...

            boolean success = fetchRegistry(remoteRegionsModified);
            if (success) {
                registrySize = localRegionApps.get().size();
                lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis();
            }

            ...       
    }
    ...
}

Eureka Server啓動過程

同Eureka Client啓動一樣,需要添加@EnableEurekaServer註解。在該類中用@Import(EurekaServerMarkerConfiguration.class)表明了程序在啓動時會先加載EurekaServerMarkerConfiguration配置類中的配置,而在該配置類中,發佈了一個標記類 EurekaServerMarkerConfiguration$Marker,該標記類會用於開啓後續EurekaServer相關配置的加載工作。

org.springframework.cloud.netflix.eureka.server.EnableEurekaServer

package org.springframework.cloud.netflix.eureka.server;
...
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

org.springframework.cloud.netflix.eureka.server.EurekaServerMarkerConfiguration

package org.springframework.cloud.netflix.eureka.server;
...
@Configuration
public class EurekaServerMarkerConfiguration {

    @Bean
    public Marker eurekaServerMarkerBean() {
        return new Marker();
    }

    class Marker {
    }
}

spring-cloud-netflix-eureka-server-{version}.jar!META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

從上面的配置中,找到會被自動加載的EurekaServerAutoConfiguration配置類

org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

package org.springframework.cloud.netflix.eureka.server;
...
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
        InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    ...
    private static String[] EUREKA_PACKAGES = new String[] { "com.netflix.discovery",
            "com.netflix.eureka" };
    ...

    /**
     * Register the Jersey filter
     */
    @Bean
    public FilterRegistrationBean jerseyFilterRegistration(
            javax.ws.rs.core.Application eurekaJerseyApp) {
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new ServletContainer(eurekaJerseyApp));
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);
        //(2)創建Filter,並匹配路徑/eureka/*
        bean.setUrlPatterns(
                Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));

        return bean;
    }

    /**
     * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
     * required by the Eureka server.
     */
    @Bean
    public javax.ws.rs.core.Application jerseyApplication(Environment environment,
            ResourceLoader resourceLoader) {

        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                false, environment);

        // Filter to include only classes that have a particular annotation.
        //
        // (1) 創建相關的web節點, 比如註冊接口/eureka/apps/{appId}
        provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
        provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));

        // Find classes in Eureka packages (or subpackages)
        //
        Set<Class<?>> classes = new HashSet<Class<?>>();
        //掃描restful接口資源的類
        for (String basePackage : EUREKA_PACKAGES) {
            Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
            for (BeanDefinition bd : beans) {
                Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
                        resourceLoader.getClassLoader());
                classes.add(cls);
            }
        }

        // Construct the Jersey ResourceConfig
        //
        Map<String, Object> propsAndFeatures = new HashMap<String, Object>();
        propsAndFeatures.put(
                // Skip static content used by the webapp
                ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
                EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");

        DefaultResourceConfig rc = new DefaultResourceConfig(classes);
        rc.setPropertiesAndFeatures(propsAndFeatures);

        return rc;
    }

    ...
}

上面的代碼主要介紹Eureka Server在啓動時,創建相關接口的過程,這些接口是用於服務註冊,服務續約,以及服務下線等。這些接口是用jersey發佈的restful接口,資源類都在com.netflix.eureka.resources包下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章