Eureka源码分析--启动流程(1)

210 阅读6分钟

Eureka源码分析--启动流程(1)

源码入口分析@EnableEurekaServer

源码:

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

其中@Import一如EurekaServerMarkerConfiguration注解

而EurekaServerMarkerConfiguration.class中只是将内部类Marker.class作为bean注入进spring中,通过查找Marker.class的引用发现,在EurekaServerAutoConfiguration中有@ConditionOnBean中引用。其含义为当IOC中存再EurekaServerMarkerConfiguration.Marker.class类是该类注入Spring中

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {

EurekaServerAutoConfiguration中存在一个@Bean修饰的方法eurekaServerBootstrap,该方法即eureka server启动的入口。

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

真正的EurekaServer启动类--EurekaBootstrap

EurekaBootstrap中存在一个很明显的方法,contextInitialzed,很明显这个方法就是eureka server启动的入口所在,但是从spring boot中并位发现在哪里调用了这个类,当去反查找时发现,这个方法存在EurekaServerInitializerConfiguration类中start()中所调用,而EurekaServerInitializerConfiguration在EurekaServerAutoConfiguration的@Import中引入进来,而这个start()方法能够运行是因为该类实现了SmartLifecycle接口,在spring初始化的时候会进行调用,从而进入EurekaBootstrap的方法中去。

EurekaServerInitializerConfiguration
@Override
	public void start() {
		new Thread(() -> {
			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();
	}

EurekaBoostrap的contextInitialized方法中存在两个主要的方法,initEurekaEnvironment()、initEurekaServerContext()。通过名字可以大致猜测出第一个方法主要在于设置eureka server的环境,第二个方法在于初始化eureka server的容器。

initEurekaEnvironment()

该方法就是设置一下配置

protected void initEurekaEnvironment() throws Exception {
        logger.info("Setting the eureka configuration..");

        //初始化ConfigurationManager的实例
        //ConfigurationManager是管理eureka自己的所有配置,读取配置文件里的配置到内存里,供后续的eureka-server使用
        String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
        if (dataCenter == null) {
            logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
        } else {
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
        }
        String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
        if (environment == null) {
            ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
            logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
        }
    }

initEurekaServerContext()

protected void initEurekaServerContext() throws Exception {
        //第一步,加载eureka-server.propertties中的配置
        EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();

        // For backward compatibility
        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);

        //第二步,初始换eureka-server内部的一个eureka-client
        ApplicationInfoManager applicationInfoManager = null;

        if (eurekaClient == null) {
            //instanceConfig对象时eureka-client.properties配置文件中的配置承载对象
            EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                    ? new CloudInstanceConfig()
                    : new MyDataCenterInstanceConfig();
            //将instanceConfig和instanceInfo保存进application
            applicationInfoManager = new ApplicationInfoManager(
                    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
            //读取eureka-client.properties配置文件作为eurekaClient的配置
            EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
            //将自己也作为eureka客户端,实例化eurekaClient对象
            eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
        } else {
            applicationInfoManager = eurekaClient.getApplicationInfoManager();
        }
        //第三步,处理注册的事物,建立能够感知集群的服务实力注册表
        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,这是一个用来管理集群内节点的工具类
        PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
                registry,
                eurekaServerConfig,
                eurekaClient.getEurekaClientConfig(),
                serverCodecs,
                applicationInfoManager
        );
        //第五步,完成eureka-server上下文的构建以及初始化
        serverContext = new DefaultEurekaServerContext(
                eurekaServerConfig,
                serverCodecs,
                registry,
                peerEurekaNodes,
                applicationInfoManager
        );
        //将context放入holder中,后续使用context时,直接从holder中获取
        EurekaServerContextHolder.initialize(serverContext);
        //初始化构造出来的server上下文
        serverContext.initialize();
        logger.info("Initialized server context");

        // Copy registry from neighboring eureka node
        //从邻近的eureka节点中拷贝注册表,如果失败,找下一个
        int registryCount = registry.syncUp();
        //定时剔除失效服务
        registry.openForTraffic(applicationInfoManager, registryCount);

        // Register all monitoring statistics.
        //注册监控项
        EurekaMonitors.registerAllStats();
    }

简单点说主要分为这几个步骤:

1、加载eureka-server.propertties中的配置

2、将自己作为eureka client进行启动

3、处理注册的事务,建立能够感知集群的服务实力注册表

4、构造peerEurekaNodes,用来管理集群内节点的工具类

5、完成eureka server上下文的构建以及初始化

6、从相邻eureka节点拷贝注册表

7、开启定时剔除失效服务

8、注册监控项

从initEurekaServerContext()方法中不难看出,1-7个步骤主要在2-6之间,所以继续分析源码会从2开始直接进行分析

在eureka server内部初始化的eureka client

//第二步,初始换eureka-server内部的一个eureka-client
        ApplicationInfoManager applicationInfoManager = null;

        if (eurekaClient == null) {
            //instanceConfig对象时eureka-client.properties配置文件中的配置承载对象
            EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
                    ? new CloudInstanceConfig()
                    : new MyDataCenterInstanceConfig();
            //将instanceConfig和instanceInfo保存进application
            applicationInfoManager = new ApplicationInfoManager(
                    instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
            //读取eureka-client.properties配置文件作为eurekaClient的配置
            EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
            //将自己也作为eureka客户端,实例化eurekaClient对象
            eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
        } else {
            applicationInfoManager = eurekaClient.getApplicationInfoManager();
        }

该方法重点在最后实例化eurekaClient对象new DiscoveryClient();进入该方法继续分析

 DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
        //默认情况args=null
        if (args != null) {
            this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
            this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
            this.eventListeners.addAll(args.getEventListeners());
            this.preRegistrationHandler = args.preRegistrationHandler;
        } else {
            this.healthCheckCallbackProvider = null;
            this.healthCheckHandlerProvider = null;
            this.preRegistrationHandler = null;
        }
        //从配置管理器中获取配置,保存到对象属性中
        this.applicationInfoManager = applicationInfoManager;
        InstanceInfo myInfo = applicationInfoManager.getInfo();

        clientConfig = config;
        staticClientConfig = clientConfig;
        transportConfig = config.getTransportConfig();
        instanceInfo = myInfo;
        if (myInfo != null) {
            appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
        } else {
            logger.warn("Setting instanceInfo to a passed in null value");
        }

        this.backupRegistryProvider = backupRegistryProvider;
        this.endpointRandomizer = endpointRandomizer;
        this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
        localRegionApps.set(new Applications());

        fetchRegistryGeneration = new AtomicLong(0);

        remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
        remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));

        if (config.shouldFetchRegistry()) {
            this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        if (config.shouldRegisterWithEureka()) {
            this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
        } else {
            this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
        }

        logger.info("Initializing Eureka in region {}", clientConfig.getRegion());

        //eureka服务端以单机方式启动;设置这两个参数为false,方法将提前返回
        if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
            logger.info("Client configured to neither register nor query for data.");
            scheduler = null;
            heartbeatExecutor = null;
            cacheRefreshExecutor = null;
            eurekaTransport = null;
            instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());

            // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
            // to work with DI'd DiscoveryClient
            DiscoveryManager.getInstance().setDiscoveryClient(this);
            DiscoveryManager.getInstance().setEurekaClientConfig(config);

            initTimestampMs = System.currentTimeMillis();
            initRegistrySize = this.getApplications().size();
            registrySize = initRegistrySize;
            logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                    initTimestampMs, initRegistrySize);

            return;  // no need to setup up an network tasks and we are done
        }

        try {
            //以普通方式启动:初始化三个线程池
            // default size of 2 - 1 each for heartbeat and cacheRefresh
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());

            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            cacheRefreshExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff

            //构造client与server网络通信组件,初始化相关通信参数
            eurekaTransport = new EurekaTransport();
            scheduleServerEndpointTask(eurekaTransport, args);

            AzToRegionMapper azToRegionMapper;
            if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
                azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
            } else {
                azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
            }
            if (null != remoteRegionsToFetch.get()) {
                azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
            }
            instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
        } catch (Throwable e) {
            throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
        }

        //服务发现,拉去注册表核心逻辑,如果拉取失败,则从备份注册中拉取
        if (clientConfig.shouldFetchRegistry()) {
            try {
                //获取注册表信息。
                //此方法尝试在第一次获取后仅获取增量,除非在协调 eureka 服务器和客户端注册表信息时存在问题。
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                if (!primaryFetchRegistryResult) {
                    logger.info("Initial registry fetch from primary servers failed");
                }
                boolean backupFetchRegistryResult = true;
                if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) {
                    backupFetchRegistryResult = false;
                    logger.info("Initial registry fetch from backup servers failed");
                }
                if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) {
                    throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed.");
                }
            } catch (Throwable th) {
                logger.error("Fetch registry error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

        // call and execute the pre registration handler before all background tasks (inc registration) is started
        if (this.preRegistrationHandler != null) {
            this.preRegistrationHandler.beforeRegistration();
        }

        if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
            try {
                if (!register() ) {
                    throw new IllegalStateException("Registration error at startup. Invalid server response.");
                }
            } catch (Throwable th) {
                logger.error("Registration error at startup: {}", th.getMessage());
                throw new IllegalStateException(th);
            }
        }

        //使用前面构造出来的线程池,执行定时调度任务,包括:1、定时获取增量注册表 2.延时向server进行服务注册
        // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
        initScheduledTasks();

        try {
            Monitors.registerObject(this);
        } catch (Throwable e) {
            logger.warn("Cannot register timers", e);
        }

        // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
        // to work with DI'd DiscoveryClient
        DiscoveryManager.getInstance().setDiscoveryClient(this);
        DiscoveryManager.getInstance().setEurekaClientConfig(config);

        initTimestampMs = System.currentTimeMillis();
        initRegistrySize = this.getApplications().size();
        registrySize = initRegistrySize;
        logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
                initTimestampMs, initRegistrySize);
    }

client初始化会走到DiscoveryClient类中,步骤分为几步:

1、从配置管理器中获取配置,保存到对象属性中

2、判断是否以单机方式启动,如果单机启动,则直接返回

3、初始化三个线程池

4、构造网络通信组件

5、从注册的eureka server中尝试拉取第一次全量注册表到本地

6、将第三部初始化的线程池执行定时调度任务:包括:1、定时获取增量注册表 2、延时向server进行服务注册

总结

eureka分为server端和client,整体来说,server端启动在集群模式下会像client端一样,而后才会运行server本身的定时任务线程服务,而client端启动并不是其重点所在,而重点在于client端如何进行服务注册,心跳续约等一系列问题。