Spring

Eureka Server启动过程

2019-07-14  本文已影响0人  王勇1024

前言

我们已经在 Spring Cloud注册发现:Eureka 一文中对Eureka的基本原理有所了解,并在 Eureka使用示例 一文中我们简单介绍了如何创建一个 Eureka Server 和 Client,并将Client注册到Server中。其中有介绍到:@EnableEurekaServer 注解开启Eureka Server的服务注册功能。下面我们就以@EnableEurekaServer 为入口,来分析一下Eureka Server的启动流程。

源码解析

EnableEurekaServer注解位于 org.springframework.cloud.netflix.eureka.server 包中

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

可以看到它导入了EurekaServerMarkerConfiguration类:

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

这个EurekaServerMarkerConfiguration会往Spring容器中注入一个eurekaServerMarkerBean,这个Marker是一个空类,那在这里起到什么作用呢?
仔细查看这个类的注释,可以看到它只是一个开关标记,用来激活EurekaServerAutoConfiguration类的。

那Eureka Server真正的启动入口在哪儿呢?
了解 Spring Factories 机制的同学可能会想到去查看 spring-cloud-starter-netflix-eureka-server jar包中的 META-INF/spring.factories 文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

可以看到 Eureka Server 启动的真正入口是 EurekaServerAutoConfiguration

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
        InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter

在这个类中,我们并没有发现与启动Eureka相关的代码,那么我们来看看它引入的这个EurekaServerInitializerConfiguration:

@Configuration
public class EurekaServerInitializerConfiguration
        implements ServletContextAware, SmartLifecycle, Ordered 

可以看到,这也是一个配置类,同时它实现了 ServletContextAware接口,可以在Servlet容器启动后得到ServletContext容器上下文;它还实现了 SmartLifecycle
,这样在spring 生命周期中会调用这个类相关的方法。比如在spring初始化时,会调用它start方法。

    @Override
    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //TODO: is this class even needed now?
                    eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
                    log.info("Started Eureka Server");
                    publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                    EurekaServerInitializerConfiguration.this.running = true;
                    publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
                }
                catch (Exception ex) {
                    // Help!
                    log.error("Could not initialize Eureka servlet context", ex);
                }
            }
        }).start();
    }

contextInitialized方法是真正执行了Eureka Server的初始化过程:

    public void contextInitialized(ServletContext context) {
        try {
            initEurekaEnvironment();
            initEurekaServerContext();

            context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
        }
        catch (Throwable e) {
            log.error("Cannot bootstrap eureka server :", e);
            throw new RuntimeException("Cannot bootstrap eureka server :", e);
        }
    }

initEurekaEnvironment() 方法从配置文件中读取并设置datacenter和environment的配置参数,过程比较简单就不再赘述。
initEurekaServerContext() 方法初始化 EurekaServer 上下文:

    protected void initEurekaServerContext() throws Exception {
        // 省略AWS相关逻辑
        EurekaServerContextHolder.initialize(this.serverContext);
        log.info("Initialized server context");
        // 从邻近的Eureka Server同步注册信息
        int registryCount = this.registry.syncUp();
        this.registry.openForTraffic(this.applicationInfoManager, registryCount);
        // 注册所有监控统计信息
        EurekaMonitors.registerAllStats();
    }

registry.syncUp() 方法用于在当前 Eureka Server 节点启动时从邻近的Eureka Server同步注册信息,并返回同步得到的应用数量。当存在多个Eureka Server时,该方法会有实际的作用,用于达到各个节点之间数据的最终一致性。可以通过 eureka.server.registry-sync-retries 配置调整同步重试次数。如果未获取到应用实例,则 Eureka-Server 会有一段时间( 默认:5 分钟,可配 )不允许被 Eureka-Client 获取注册信息,避免影响 Eureka-Client 。详细过程请参考:Eureka-Server 集群同步

registry.openForTraffic() 方法
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

    @Override
    public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
        // 计算每分钟最小续租次数。例如,如果30s续租一次,
       // 那么每分钟最小次数为2。
        this.expectedNumberOfClientsSendingRenews = count;
        updateRenewsPerMinThreshold();
        logger.info("Got {} instances from neighboring DS node", count);
        logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
        this.startupTime = System.currentTimeMillis();
        if (count > 0) {
            this.peerInstancesTransferEmptyOnStartup = false;
        }
        DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
        // 如果数据中心为 AWS,则为所有副本启动AWS连接
        boolean isAws = Name.Amazon == selfName;
        if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
            logger.info("Priming AWS connections for all replicas..");
            primeAwsReplicas(applicationInfoManager);
        }
        logger.info("Changing status to UP");
        // 更新实例状态为UP
     
   applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
        super.postInit();
    }

此时,Eureka Server已经启动,开始接受Eureka Client的注册、续租以及查询请求。为了保证Eureka Client的健康存活,Eureka Server会启动定时任务清理租约信息,淘汰失效的Eureka Client。

    protected void postInit() {
        // 启动定时任务,定期清理租约信息
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        // 
        evictionTaskRef.set(new EvictionTask());
        evictionTimer.schedule(evictionTaskRef.get(),
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
    }
上一篇 下一篇

猜你喜欢

热点阅读