Spring Cloud Eureka源代码解析(1)Eureka启动,原生启动与SpringCloudEureka启动异同

Wesley13
• 阅读 835

Eureka作为服务注册中心对整个微服务架构起着最核心的整合作用,因此对Eureka还是有很大的必要进行深入研究。

Eureka 1.x版本是纯基于servlet的应用。为了与spring cloud结合使用,除了本身eureka代码,还有个粘合模块spring-cloud-netflix-eureka-server。在我们启动EurekaServer实例的时候,只用加入对于spring-cloud-starter-eureka-server的依赖即可。之后通过@EnableEurekaServer注解即可启动一个Eureka服务器实例。先来看看这个注解是如何启动一个Eureka服务的

Eureka启动,原生启动与SpringCloudEureka启动异同

我们先看看作为原生的EurekaServer启动的过程,作为一个Servlet应用,他的启动入口就是他的主要ServletContextListener类(这里是EurekaBootStrap)的contextInitialized方法

@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需要的所有组件:

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();
}

总结下来,总共如下几点:

1.初始化并设置序列化反序列化工具
2.初始化通信客户端EurekaClient
3.初始化集群通信类PeerAwareInstanceRegistry与PeerEurekaNodes
4.初始化EurekaServer上下文serverContext
5.从其他节点中读取注册信息,并开放服务注册

然后,由于原生的EurekaServer利用Jersey框架初始化restApi,这里还有:
6.载入Jersey,初始化Restful服务api

我们先不谈就里面的细节,先看看在Spring-cloud下的eureka初始化是否有区别:

对于胶水代码,实现了大致同样的但是略微有些区别的功能:

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

}

@EnableEurekaServer注解主要是引入EurekaServerMarkerConfiguration这个配置类,而这个配置类也很简单:

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

  2. 初始化集群通信类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做了自己的内部缓存。两个功能的机制如图:

Spring Cloud Eureka源代码解析(1)Eureka启动,原生启动与SpringCloudEureka启动异同

而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请求(服务维持注册)的次数

这个自我保护机制是这样的:
Spring Cloud Eureka源代码解析(1)Eureka启动,原生启动与SpringCloudEureka启动异同

每次每个服务新注册时,会给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初始化基本流程,下一张我们会更深入针对每个组件进行分析

点赞
收藏
评论区
推荐文章
Easter79 Easter79
2年前
SpringCloud注册中心高可用搭建
SpringCloud的注册中心可以由Eureka、Consul、Zookeeper、ETCD等来实现,这里推荐使用SpringCloudEureka来实现注册中心,它基于Netfilix的Eureka做了二次封装,完成分布式服务中服务治理的功能,微服务系统中的服务注册与发现都通过这个注册中心来进行管理。引入EurekaServer依赖
Stella981 Stella981
2年前
Spring Cloud Eureka 全解 (6)
本文基于SpringCloudDalston.SR5(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fcloud.spring.io%2Fspringcloudstatic%2FDalston.SR5%2F)Eureka服务实例启动时,是否会立刻向EurekaServer注册?
Stella981 Stella981
2年前
Spring Cloud系列教程(六):服务注册与发现Consul(Finchley版本)
一、前言在微服务领域,服务注册与发现是其中很重要的一个模块,主要用于服务治理问题;在分布式Dubbo中常用的服务发现与注册中心是Zookeeper,Cosul与其类似,在SpringCloud刚占领市场的时候,SpringCloud微服务框架默认使用的注册中心组建是Eureka,总所周知,Eureka已经开始闭源了,
Easter79 Easter79
2年前
SpringCloud常用组件
springcloud中有五大核心组件Eureka、Ribbon、Feign、Hystrix、Zuul,简单记录如下。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。EurekaClient组件专门负责将服务的信息注册到EurekaServer中,而EurekaServer是一个注册中心,里面有一个注册表,保存了各服务所在
Easter79 Easter79
2年前
SpringCloud学习(3)——Eureka服务注册中心及服务发现
Eureka概述Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务框架来说非常重要,有了服务注册和发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于
Stella981 Stella981
2年前
Spring Cloud Eureka 服务关闭但是未从注册中心删除 自我保护机制
自我保护背景首先对Eureka注册中心需要了解的是Eureka各个节点都是平等的,没有ZK中角色的概念,即使N1个节点挂掉也不会影响其他节点的正常运行。默认情况下,如果EurekaServer在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,EurekaServer将会移除该实例。但是当网络分区故障发生时,微服务与EurekaSer
Easter79 Easter79
2年前
SpringCloud学习笔记(七)之路由网关Zuul
是什么Zuul包含了对请求路由和过滤两个最主要的功能:其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。而过滤功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。Zuul和Eureka进行整合,将zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微
Easter79 Easter79
2年前
SpringCloud 服务通信方法
eureka服务已经启动 http://localhost:8761/注:在启动文件加上注解@EnableEurekaServerapplication.yml配置文件eureka:instance:hostname:localhostclient:se
Stella981 Stella981
2年前
Spring Cloud Eureka的基础架构
基础架构服务注册中心:Eureka提供的服务端,提供服务注册于发现的功能,也就是在上一节中我们实现的eurekaserver服务提供者:提供服务的应用,可以是springBoot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现,也