1:使用SpringCloud框架的程序,除了Eureka服务之外,其余的服务都是Eureka Client。默认的情况,会向Eureka注册自己的服务实例,并从Eureka服务器周期性的获取注册信息。
在应用程序 引入 spring-cloud-starter-eureka 依赖包,即可引入 Eureka Clinet:spring-cloud-netflix-eureka-client。(可能不同的版本,依赖的包名称不一样)。如下:
org.springframework.cloud
spring-cloud-starter-eureka
2:在spring-cloud-netflix-eureka-client包的 spring.factories里,代码如下。在这里里面,会执行自动配置加载类:EurekaClientAutoConfiguration。这里主要就是注册Eureka客户端需要的对象。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
org.springframework.cloud.client.discovery.EnableDiscoveryClient=
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
3:默认情况,配置 eureka.client.enabled 的值是true。在yml文件以 eureka.client 开头的配置,解析的对象是EurekaClientConfigBean。
其默认配置如下:
registryFetchIntervalSeconds:从erureka服务端获取服务实例的频率。默认情况下。每个30秒一次
eurekaServerReadTimeoutSeconds:从eureka服务端读取获取信息的超时时间,默认:8秒
eurekaServerConnectTimeoutSeconds:连接eureka服务器的超时时间,默认:5秒
serviceUrl:eureka服务注册的URL:默认地址是:http://localhost:8761/eureka。这里是个MAP,可以配置多个地址
eurekaServerTotalConnections:连接到eureka的总连接数,默认:200个
eurekaServerTotalConnectionsPerHost:连接单个eureka的连接数,默认:50个
registerWithEureka:是否注册到eureka,默认值:true
fetchRegistry:是否从eureka服务端获取注册信息,默认值:true.
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, EurekaConstants {
public static final String PREFIX = "eureka.client";
@Autowired(required = false)
PropertyResolver propertyResolver;
public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX
+ "/";
public static final String DEFAULT_ZONE = "defaultZone";
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(propertyResolver.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later. client.setRegisterWithEureka(false);
}
return client;
}
4:注册DiscoveryClient实例,可以通过该实例对象,从缓存中获取在eureka上注册的服务信息。~~~~
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
5:注册EurekaClient实例,类型是CloudEurekaClient。其父类是 DiscoveryClient。在该类的构造函数里,会建立注册到eureka和从eureka获取服务服务的定时任务。
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}
6:在 DiscoveryClient 的构造函数里,除了各种属性赋值外,还调用了一个非常重要的方法initScheduledTasks()来初始化定时任务,当然,如果该应用的registerWithEureka和fetchRegistry都是false,则不不会执行该方法。
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) { //fetchRegistry配置的是true,则执行
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); //默认30S,从eureka获取注册信息间隔时间
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); //默认值是10,
scheduler.schedule( //建立任务,从eureka获取注册信息,初始延迟时间,30S。任务类:TimedSupervisorTask
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) { //registerWithEureka配置的值是true则执行。
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); //默认30秒
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule( //注册心跳定时任务,定时向eureka发送信息,初始延迟时间30S
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
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) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) { //注册事件监听器,按照上面的代码,如果发现状态是DOWN,会立刻注册到eureka
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); //建立定时任务,初始延迟时间是40S
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
7:实际上上面代码中,TimedSupervisorTask类实际执行的是类: CacheRefreshThread 和 HeartbeatThread 的run 方法。 这里就不展开。 代码也不难。
8:在自动化配置中,同时也会实例化 EurekaAutoServiceRegistration 对象。该对象监听了Spring的事件:EmbeddedServletContainerInitializedEvent。当WEB容器初始化完成后,会发送此事件。 在该事件处理方法中,会把当前节点注册到 eureka服务器中。
@EventListener(EmbeddedServletContainerInitializedEvent.class)
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
// TODO: take SSL into account when Spring Boot 1.2 is available
int localPort = event.getEmbeddedServletContainer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0, localPort);
start();
}
}
public void start() {
// only set the port if the nonSecurePort is 0 and this.port != 0
if (this.port.get() != 0 && this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
this.serviceRegistry.register(this.registration);
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
}