“不积跬步,无以至千里。”
说完了Eureka Server的启动,现在再来看看Eureka Client是怎么启动的。
出发点是eureka-examples
这个模块的一个Example类:ExampleEurekaClient
。
这个类相当于模拟了我们平时写的客户端程序,所以我们找到这个main()方法里的代码,启动流程的逻辑就在这个main()方法里;
public static void main(String[] args) {
ExampleEurekaClient sampleClient = new ExampleEurekaClient();
// create the client
ApplicationInfoManager applicationInfoManager = initializeApplicationInfoManager(new MyDataCenterInstanceConfig());
EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());
// use the client
sampleClient.sendRequestToServiceUsingEureka(client);
// shutdown the client
eurekaClient.shutdown();
}
同样,我在这里梳理这个启动方法做的几件事情:
(1)读取eureka-client.properties配置文件,形成一个服务实例的配置,基于接口对外提供服务实例的配置项的读取,这个上一篇文章讲过,基于getXxx()的方法提供配置项读取是Eureka配置管理的套路;
new MyDataCenterInstanceConfig()
(2)基于服务实例的配置,构造了一个服务实例(InstanceInfo)
(3)基于服务实例的配置和服务实例,构造了一个服务实例管理器(ApplicationInfoManager)
private static synchronized ApplicationInfoManager initializeApplicationInfoManager(EurekaInstanceConfig instanceConfig) {
if (applicationInfoManager == null) {
//构造服务实例
InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
//构造服务实例管理器
applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
}
return applicationInfoManager;
}
(4)读取eureka-client.properites配置文件,构建为一个DynamicPropertyFactory
的配置类,接口对外提供eureka client的配置项的读取;
然后基于EurekaClient配置类构建了网络通信组件的配置类:EurekaTransportConfig
,Eureka client启动,必然要向Eureka Server去注册服务以及抓取注册表,所以在这里一定会构建网络通信相关的东西;
public DefaultEurekaClientConfig(String namespace) {
this.namespace = namespace.endsWith(".")
? namespace
: namespace + ".";
//private final DynamicPropertyFactory configInstance;
//public static final String CONFIG_FILE_NAME = "eureka-client";
this.configInstance = Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
this.transportConfig = new DefaultEurekaTransportConfig(namespace, configInstance);
}
(5)把上面构建的ApplicationInfoManager和EurekaClientConfig作为参数,初始化Eureka client,使用的是子类DiscoveryClient
;
EurekaClient client = initializeEurekaClient(applicationInfoManager, new DefaultEurekaClientConfig());
private static synchronized EurekaClient initializeEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig clientConfig) {
if (eurekaClient == null) {
eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
}
return eurekaClient;
}
我们来看一下DiscoveryClient
的构造流程,看看初始化eureka客户端都干了些啥:
this.applicationInfoManager = applicationInfoManager;
InstanceInfo myInfo = applicationInfoManager.getInfo();
//config就是EurekaClientConfig
clientConfig = config;
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig();
instanceInfo = myInfo;
先保存ApplicationInfoManager、EurekaClientConfig、EurekaTransportConfig、InstanceInfo这些东西,可能后面会使用;
然后判断是否要注册以及抓取注册表,如果不要的话,就会在这里释放一些资源;
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
... ...
}
然后创建了一些调度线程池;
//支持调度的线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
//支持心跳的线程池
ThreadPoolExecutor heartbeatExecutor = new ThreadPoolExecutor(1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true) .build());
//支持缓存的线程池
ThreadPoolExecutor cacheRefreshExecutor = new ThreadPoolExecutor(1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),new ThreadFactoryBuilder().setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());
随后初始化网络通信组件EurekaTransport
,这个通信组件是一个核心组件,后面服务注册与发现、心跳等都会基于这个组件里的一系列HttpClient组件进行客户端和服务端的网络通信,这个通信使用的是Http Restful接口调用的方式,基于jersey
进行的,jersey也是一种web mvc框架,和spring mvc是属于同一种类。
private final EurekaTransport eurekaTransport;
... ...
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
如果配置了要抓取注册表的话,在这里就会去抓取注册表了;
if (clientConfig.shouldFetchRegistry()) {
try {
boolean primaryFetchRegistryResult = fetchRegistry(false);
if (!primaryFetchRegistryResult) {
logger.info("Initial registry fetch from primary servers failed");
}
... ...
}
如果配置了向Eureka server注册,就会在这里注册;
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if (!register() ) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
... ...
}
初始化调度任务:如果需要抓取注册表的话,就会开启一个定时任务,每隔一定时间(默认是30s),去执行一个CacheRefreshThread
线程类的run方法,然后将这个Task放进一个调度线程池里;
如果要向Eureka server进行注册的话,同样的搞一个定时任务,每隔一定时间发送心跳,执行一个HeartbeatThread
线程类的run方法;
initScheduledTasks();
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//抓取注册表任务
cacheRefreshTask = new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
);
scheduler.schedule(
cacheRefreshTask,
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
//发送心跳任务
heartbeatTask = new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
);
scheduler.schedule(
heartbeatTask,
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
logger.info("Saw local status change event {}", statusChangeEvent);
instanceInfoReplicator.onDemandUpdate();
}
};
... ...
}
最后附上一张自绘制的EurekaClient启动流程图帮助理解。