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端如何进行服务注册,心跳续约等一系列问题。