欢迎访问Spring Cloud中国社区

《重新定义Spring Cloud实战》由Spring Cloud中国社区倾力打造,基于Spring Cloud的Finchley.RELEASE版本,本书内容宽度足够广、深度足够深,而且立足于生产实践,直接从生产实践出发,包含大量生产实践的配置。欢迎加微信Software_King进群答疑,国内谁在使用Spring Cloud?欢迎登记

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

saleson · 5月前 · 1723 ·

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包下。