spring · 15 8 月, 2021 0

spring cloud 服务注册之Eureka Server(一) – 自动装配

在微服务开发过程中,始终少不了注册中心的存在,注册中心提供了服务的注册与发现机制,能够不需要代码改动,实现服务的横向扩展,同时也为微服务之间的调用解耦,避免了服务调用之间的高度依赖。本文主要从源码的角度出发,对spring-cloud-netflex-eureka-server中的源码进行解读,学习eureka中服务注册实现逻辑.

启动EurekaServer

完整的demo可以通过spring-cloud-learn项目查看

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class SpringEurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringEurekaServerApplication.class, args);
    }
}

当启动eureka-server时,需要通过@EnableEurekaServer注解的方式显示启动spring-cloud-netflix-eureka-server装配.

启动过程

@EnableEurekaServer

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

}

通过该注解,将会@Import一个Configuration类型的初始化与创建工作.

 

EurekaServerMarkerConfiguration

@Configuration
public class EurekaServerMarkerConfiguration {

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

    class Marker {

    }
}

该Configuration类型中,只是创建一个Marker的Bean对象,并没有做其他的操作.

 

EurekaClientAutoConfiguration

在装配Eureka Server时,会优先触发Eureka Client 的装配信息,通过对EurekaServerAutoConfiguration类型进行查看,是因为依赖了Eureka Client中的装配信息

   @Autowired
private ApplicationInfoManager applicationInfoManager;

@Autowired
private EurekaClientConfig eurekaClientConfig;

@Autowired
private EurekaClient eurekaClient;

这里依赖了Eureka Client相关的类信息,是因为在Eureka Server设计中,他本身也会作为Client端向其他的Eureka Server同步当前的Instance的信息。

 

EurekaClientConfigBean

在自动装配过程中, 需要从配置文件、命令行等多种渠道读取配置文件信息,Spring并没有采取Eureka Client本身的配置类,而是以EurekaClientConfgBean的方式作为配置类型, 代码如下:

@Bean
    @ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
    public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
        EurekaClientConfigBean client = new EurekaClientConfigBean();
        if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
            // We don't register during bootstrap by default, but there will be another
            // chance later.
            client.setRegisterWithEureka(false);
        }
        return client;
    }

@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {...}

对于EurekaClientConfigBean而言,是一个Properties类型的承载类型, 将配置中eureka.client的配置,设置到当前类型中.

 

ApplicationInfoManager

    @Configuration
    // 依赖于Spring Cloud RefreshScope
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired
        private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;


        @Bean
        @ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
        @org.springframework.cloud.context.config.annotation.RefreshScope
        @Lazy // 虽然加入了lazy, 但是eureka server在启动时,明确依赖了,因此会触发实例化
        public ApplicationInfoManager eurekaApplicationInfoManager(
                EurekaInstanceConfig config) {
            InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
            return new ApplicationInfoManager(config, instanceInfo);
        }

    }

ApplicationInfoManager通过InstanceInfoFactory工厂方法进行创建, 在SpringCloud中,因为Bean被RefreshScope注解修饰,能够是的ApplicationInfoManager的Bean能够在运行时环境更新,并让其他的依赖Bean使用的新的实例进行操作.

 

EurekaClient

@Configuration
    @ConditionalOnRefreshScope
    protected static class RefreshableEurekaClientConfiguration {

        @Autowired
        private ApplicationContext context;

        @Autowired
        private AbstractDiscoveryClientOptionalArgs<?> 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,
                @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
            // If we use the proxy of the ApplicationInfoManager we could run into a
            // problem
            // when shutdown is called on the CloudEurekaClient where the
            // ApplicationInfoManager bean is
            // requested but wont be allowed because we are shutting down. To avoid this
            // we use the
            // object directly.
            ApplicationInfoManager appManager;
            if (AopUtils.isAopProxy(manager)) {
                appManager = ProxyUtils.getTargetObject(manager);
            }
            else {
                appManager = manager;
            }
            CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager,
                    config, this.optionalArgs, this.context);
            cloudEurekaClient.registerHealthCheck(healthCheckHandler);
            return cloudEurekaClient;
        }
    }

EurekaClient与ApplicationInfoManager的创建很相似, 都是支持在运行时环境进行Bean实例的更新.

 

EurekaServerAutoConfiguration

在以上Eureka Client 相关基础Bean创建完成之后, 此时将会触发Eureka Server 相关的装配信息, Eureka Server中主要包含了几个重要的类型:

 

EurekaServerConfigBean

@Configuration
    protected static class EurekaServerConfigBeanConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
            EurekaServerConfigBean server = new EurekaServerConfigBean();
            if (clientConfig.shouldRegisterWithEureka()) {
                // Set a sensible default if we are supposed to replicate
                server.setRegistrySyncRetries(5);
            }
            return server;
        }

    }

EurekaServerConfigBean类型,主要用于存储eureka.server相关的配置项,通过@ConfigurationProperties(EurekaServerConfigBean.PREFIX)的方式读取配置信息.

 

PeerAwareInstanceRegistry

@Bean
    public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
            ServerCodecs serverCodecs) {
        this.eurekaClient.getApplications(); // force initialization
        return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
                serverCodecs, this.eurekaClient,
                this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
                this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
    }

该类主要用于存储Eureka Client上报的Instance 信息, 具体信息在后续会进行讲解.

 

PeerEurekaNodes

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

PeerEurekaNodes主要用来保存其他的Eureka Server节点信息,并定期做更新操作.

 

EurekaServerContext

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

EurekaServerContext上下文信息,主要是在EurekaServer启动后,根据PeerEurekaNodes信息从其他Eureka Eureka Server上同步已有instance节点信息。

 

EurekaServerBootstrap

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

该类型主要负责Eureka Server启动后,从其他的Eureka Node上同步instance信息,并启动定时任务, 执行evict将已经下线的任务从registry中剔除。

将Jersey中的定义转换为Spring MVC

@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.
     * @param environment an {@link Environment} instance to retrieve classpath resources
     * @param resourceLoader a {@link ResourceLoader} instance to get classloader from
     * @return created {@link Application} object
     */
    @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<>();
        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<>();
        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;
    }

Spring Cloud为了能够与Jersey的融合,一起使用,因此通过手动创建Applicaion的方式,将classpath下所有的@Path@Provider注解的信息,映射到Servlet Filter中作统一的处理。

以上就是spring-cloud-netflix-eureka-server的整体装配过程,下一篇文章将具体介绍每个核心类型的作用以及三级缓存的作用。