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

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);
}
  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初始化基本流程,下一张我们会更深入针对每个组件进行分析

转载于:https://my.oschina.net/u/3747772/blog/1588933

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值