Spring Cloud Eureka源代碼解析(1)Eureka啓動,原生啓動與SpringCloudEureka啓動異同

Eureka做爲服務註冊中心對整個微服務架構起着最核心的整合做用,所以對Eureka仍是有很大的必要進行深刻研究。css

Eureka 1.x版本是純基於servlet的應用。爲了與spring cloud結合使用,除了自己eureka代碼,還有個粘合模塊spring-cloud-netflix-eureka-server。在咱們啓動EurekaServer實例的時候,只用加入對於spring-cloud-starter-eureka-server的依賴便可。以後經過@EnableEurekaServer註解便可啓動一個Eureka服務器實例。先來看看這個註解是如何啓動一個Eureka服務的java

Eureka啓動,原生啓動與SpringCloudEureka啓動異同

咱們先看看做爲原生的EurekaServer啓動的過程,做爲一個Servlet應用,他的啓動入口就是他的主要ServletContextListener類(這裏是EurekaBootStrap)的contextInitialized方法node

@Override
public void contextInitialized(ServletContextEvent event) {
    try {
        initEurekaEnvironment();
        initEurekaServerContext();

        ServletContext sc = event.getServletContext();
        sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
    } catch (Throwable e) {
        logger.error("Cannot bootstrap eureka server :", e);
        throw new RuntimeException("Cannot bootstrap eureka server :", e);
    }
}

能夠看出主要作了兩件事,initEurekaEnvironment()與initEurekaServerContext()
對於initEurekaEnvironment()只是初始化一些必要的環境變量,因爲Eureka配置基於Spring的配置中間件Archaius,這些環境變量都是針對這個配置中間件使用的。
initEurekaServerContext()是咱們重點須要關心的,它初始化了EurekaServer須要的全部組件:git

protected void initEurekaServerContext() throws Exception {
    EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

    //設置json與xml序列化工具
    JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
    XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
    logger.info("Initializing the eureka client...");
    logger.info(eurekaServerConfig.getJsonCodecName());
    ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);


    ApplicationInfoManager applicationInfoManager = null;

    //初始化EurekaClient,EurekaClient用來與其餘EurekaServer進行交互
    //有可能經過guice初始化Eureka,這時eurekaClient和ApplicationInfoManager經過依賴注入先被初始化
    if (eurekaClient == null) {
        EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                ? new CloudInstanceConfig()
                : new MyDataCenterInstanceConfig();

        applicationInfoManager = new ApplicationInfoManager(
                instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());

        EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
        eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
    } else {
        applicationInfoManager = eurekaClient.getApplicationInfoManager();
    }

    //初始化PeerAwareInstanceRegistry, 這個類裏面的方法就是與集羣內其餘EurekaServer實例保持業務同步的機制
    PeerAwareInstanceRegistry registry;
    if (isAws(applicationInfoManager.getInfo())) {
        registry = new AwsInstanceRegistry(
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                eurekaClient
        );
        awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
        awsBinder.start();
    } else {
        registry = new PeerAwareInstanceRegistryImpl(
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                eurekaClient
        );
    }

    //初始化PeerEurekaNodes,裏面有定時維護Eureka集羣的業務邏輯
    PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
            registry,
            eurekaServerConfig,
            eurekaClient.getEurekaClientConfig(),
            serverCodecs,
            applicationInfoManager
    );


    //初始化EurekaServer上下文
    serverContext = new DefaultEurekaServerContext(
            eurekaServerConfig,
            serverCodecs,
            registry,
            peerEurekaNodes,
            applicationInfoManager
    );

    EurekaServerContextHolder.initialize(serverContext);

    serverContext.initialize();
    logger.info("Initialized server context");

    //從其餘節點中讀取註冊信息,並開放服務註冊
    // Copy registry from neighboring eureka node
    int registryCount = registry.syncUp();
    registry.openForTraffic(applicationInfoManager, registryCount);

    // Register all monitoring statistics.
    EurekaMonitors.registerAllStats();
}

總結下來,總共以下幾點:github

1.初始化並設置序列化反序列化工具
2.初始化通訊客戶端EurekaClient
3.初始化集羣通訊類PeerAwareInstanceRegistry與PeerEurekaNodes
4.初始化EurekaServer上下文serverContext
5.從其餘節點中讀取註冊信息,並開放服務註冊web

而後,因爲原生的EurekaServer利用Jersey框架初始化restApi,這裏還有:
6.載入Jersey,初始化Restful服務apispring

咱們先不談就裏面的細節,先看看在Spring-cloud下的eureka初始化是否有區別:json

對於膠水代碼,實現了大體一樣的可是略微有些區別的功能:bootstrap

@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

@EnableEurekaServer註解主要是引入EurekaServerMarkerConfiguration這個配置類,而這個配置類也很簡單:api

@Configuration
public class EurekaServerMarkerConfiguration {

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

    class Marker {
    }
}

這個符合基本上全部Spring-cloud生態圈的starter套路。經過一個相似於Marker的bean來啓用某個組件。核心啓動經過ConditionalOnBean來加載某些配置,在這裏這個類是:
EurekaServerAutoConfiguration,因爲Eurekaserver自己是一個Servlet應用,這個類至關於膠水代碼,將Eurekaserver須要初始化的類載入到Spring容器中管理。對於一個Servlet應用,主要初始化入口就是實現ServletContextListener的類,對於Eurekaserver是EurekaBootStrap,EurekaBootStrap初始化Eurekaserver須要的類。EurekaServerAutoConfiguration至關於將EurekaBootStrap初始化的類也初始化,同時載入到Spring容器中管理。

同時,因爲原有的EurekaServer的接口依賴Jersey,這裏的EurekaServerAutoConfiguration也要掃描Jersey實現其應該暴露的接口。同時,spring-cloud-starter-eureka-server有本身的界面,並無使用原有的Eureka界面,也是在這個類裏面加載的配置。

因此,這裏加載的Bean有:

1.Eureka DashBoard,其實就是一個Controller:
這個控制檯就是咱們經過springcloud啓動eureka以後,經過瀏覽器訪問eureka暴露的端口,看到的,例如這個:
http://eureka.didispace.com/

能夠看出,這個Controller只有eureka.dashboard.enable=true的時候纔會加載,若是不想啓用控制檯能夠設置爲false

@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
    return new EurekaController(this.applicationInfoManager);
}
  1. 序列化反序列化工具:
@Bean
public ServerCodecs serverCodecs() {
    return new CloudServerCodecs(this.eurekaServerConfig);
}
  1. 初始化集羣通訊類PeerAwareInstanceRegistry與PeerEurekaNodes:
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
        ServerCodecs serverCodecs) {
    this.eurekaClient.getApplications(); // force initialization
    return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
            serverCodecs, this.eurekaClient,
            this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
            this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}

@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
        ServerCodecs serverCodecs) {
    return new PeerEurekaNodes(registry, this.eurekaServerConfig,
            this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}

初始化PeerAwareInstanceRegistry代碼中,咱們看到this.eurekaClient.getApplications(); 其實這段代碼沒有必要寫,已經沒有預期的做用了。咱們先來看一下EurekaClient基本原理,一樣的,這裏也是簡單過一下,往後會詳細分析。
EurekaClient的默認實現是DiscoveryClient,這個能夠經過查看EurekaClientAutoConfiguration看到。
EurekaClient主要功能就是兩個:一個是從Eurekaserver上面獲取全部註冊的服務,另外一個是將本身的服務註冊到Eurekaserver上面。因爲每次都是獲取全集,因此在註冊的服務很是多的時候,這個對網絡流量和Eurekaserver性能消耗比較大。因此每一個EurekaClient作了本身的內部緩存。兩個功能的機制如圖:

image

而this.eurekaClient.getApplications();只是簡單的讀取一下Eurekaserver全部註冊的服務信息緩存(一個AtomicReference類),我的感受沒什麼做用,猜測是原來的EurekaClient代碼沒有Eurekaserver全部註冊的服務信息緩存,調用getApplications()就是從服務器網絡讀取,然後來EurekaClient更新了本身的代碼,加入了緩存,而膠水代碼沒有更新。

並且,目前的Eureka原生代碼中已經在Eurekaclient初始化的時候就強制讀取一次網絡獲取Eurekaserver的全部註冊的服務信息。這段膠水代碼就更沒有必要了。

以後,注意這裏實現類是InstanceRegistry而不是PeerAwareInstanceRegistryImpl。InstanceRegistry繼承了PeerAwareInstanceRegistryImpl,並修正了原生Eureka一些設計上的與SpringCloud不兼容的地方,並且增長了context事件爲了之後作新功能作準備(猜想)。

首先,先簡單過一下PeerAwareInstanceRegistry的功能,往後咱們還會更細緻的剖析:
PeerAwareInstanceRegistry主要負責集羣中每個EurekaServer實例的服務註冊信息的同時,而且實現了一個很著名很重要的機制:自我保護機制。

先介紹兩個變量:expectedNumberOfRenewsPerMin和numberOfRenewsPerMinThreshold。其中numberOfRenewsPerMinThreshold就是RenewalPercentThreshold*numberOfRenewsPerMinThreshold;RenewalPercentThreshold是可配置的一個介於0,1之間double類型參數。還有一個計數變量renewsLastMin,記錄了上一分鐘收到的renew請求(服務維持註冊)的次數

這個自我保護機制是這樣的:
image

每次每一個服務新註冊時,會給expectedNumberOfRenewsPerMin加2的緣由是默認半分鐘服務向Eurekaserver心跳Renew一次。

還有另外一個機制,就是在啓動時,默認會從其餘集羣節點上面讀取全部服務註冊信息。若是一個節點都沒有訪問成功(例如這個啓動的節點就是集羣中的第一個節點),這時peerInstancesTransferEmptyOnStartup就會爲true,就會禁止用戶註冊,直到集羣中有其餘節點或者超過WaitTimeInMsWhenSyncEmpty設置的時間。

這個機制顯然不夠友好,因此膠水代碼初始化PeerAwareInstanceRegistry的擴展InstanceRegistry,加入了兩個配置參數,
ExpectedNumberOfRenewsPerMin和DefaultOpenForTrafficCount。ExpectedNumberOfRenewsPerMin默認爲1,這個爲了初始化
expectedNumberOfRenewsPerMin爲一個大於0的數,這樣expectedNumberOfRenewsPerMin在單機模式下也會刷新,這個很簡單,看一下代碼就知道,這裏再也不贅述。重點說一下DefaultOpenForTrafficCount,這個默認爲1。看下InstanceRegistry的代碼:

public InstanceRegistry(EurekaServerConfig serverConfig,
            EurekaClientConfig clientConfig, ServerCodecs serverCodecs,
            EurekaClient eurekaClient, int expectedNumberOfRenewsPerMin,
            int defaultOpenForTrafficCount) {
    super(serverConfig, clientConfig, serverCodecs, eurekaClient);

    this.expectedNumberOfRenewsPerMin = expectedNumberOfRenewsPerMin;
    this.defaultOpenForTrafficCount = defaultOpenForTrafficCount;
}

public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
    super.openForTraffic(applicationInfoManager,
            count == 0 ? this.defaultOpenForTrafficCount : count);
}

構造器初始化配置,openForTraffic在原生代碼是以前提到的EurekaBootstrap contextInitialized代碼調用的;這裏是在EurekaServerInitializerConfiguration裏面初始化。這裏的openForTraffic將原來爲0的參數改成defaultOpenForTrafficCount就是1,傳入原來的openForTraffic方法。這樣保證了Eurekaserver即便是單例也能馬上正常工做;由於在單利模式下,原來的openForTraffic方法傳入的參數爲0(能夠參考以前列出的EurekaBootstrap contextInitialized代碼)

4.Eureka運行上下文

@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
        PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
    return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
            registry, peerEurekaNodes, this.applicationInfoManager);
}

5.Eureka啓動類

@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
        EurekaServerContext serverContext) {
    return new EurekaServerBootstrap(this.applicationInfoManager,
            this.eurekaClientConfig, this.eurekaServerConfig, registry,
            serverContext);
}

6.Jersey暴露接口初始化
以後咱們會重點關注暴露的接口

@Bean
public FilterRegistrationBean jerseyFilterRegistration(
        javax.ws.rs.core.Application eurekaJerseyApp) {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new ServletContainer(eurekaJerseyApp));
    bean.setOrder(Ordered.LOWEST_PRECEDENCE);
    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.
    //
    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<?>>();
    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;
}
@Bean
public FilterRegistrationBean traceFilterRegistration(
        @Qualifier("webRequestLoggingFilter") Filter filter) {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(filter);
    bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    return bean;
}

以上就是Eureka初始化基本流程,下一張咱們會更深刻針對每一個組件進行分析

相關文章
相關標籤/搜索