版本:
springCloud:Finchley
springboot:2.0.3
之前已将说过了springboot的自动配置,Springcloud也是实现了类似的自动化配置。
我们进入spring-cloud-netflix-eureka-client-2.0.0.jar下面的META-INF/spring-factories文件会发现如下内容:
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.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
我们知道springboot项目启动时会加载可以为org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置类。所以上面那几个配置类只要符合条件就会加载配置。
我们重点关注EurekaClientAutoConfiguration,主要代码
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
ManagementMetadataProvider managementMetadataProvider) {
String hostname = getProperty("eureka.instance.hostname");
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
String ipAddress = getProperty("eureka.instance.ip-address");
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
String serverContextPath = env.getProperty("server.context-path", "/");
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = env.getProperty("management.server.context-path");// nullable. should be wrapped into optional
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
instance.setSecurePortEnabled(isSecurePortEnabled);
if (StringUtils.hasText(ipAddress)) {
instance.setIpAddress(ipAddress);
}
if(isSecurePortEnabled) {
instance.setSecurePort(serverPort);
}
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
serverContextPath, managementContextPath, managementPort);
if(metadata != null) {
instance.setStatusPageUrl(metadata.getStatusPageUrl());
instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
if(instance.isSecurePortEnabled()) {
instance.setSecureHealthCheckUrl(metadata.getSecureHealthCheckUrl());
}
Map metadataMap = instance.getMetadataMap();
if (metadataMap.get("management.port") == null) {
metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
}
}
setupJmxPort(instance, jmxPort);
return instance;
}
@Bean
public DiscoveryClient discoveryClient(EurekaInstanceConfig config, EurekaClient client) {
return new EurekaDiscoveryClient(config, client);
}
@Bean
public EurekaServiceRegistry eurekaServiceRegistry() {
return new EurekaServiceRegistry();
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient, CloudEurekaInstanceConfig instanceConfig, ApplicationInfoManager applicationInfoManager, ObjectProvider healthCheckHandler) {
return EurekaRegistration.builder(instanceConfig)
.with(applicationInfoManager)
.with(eurekaClient)
.with(healthCheckHandler)
.build();
}
@Configuration
@ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs> optionalArgs;
@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);
}
@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
public ApplicationInfoManager eurekaApplicationInfoManager(
EurekaInstanceConfig config) {
InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
return new ApplicationInfoManager(config, instanceInfo);
}
}
我们可以看到在该类中配置了
EurekaClientConfigBean: 配置类,负责eureka.client.x配置
EurekaInstanceConfigBean: 配置类,负责eureka.instance配置
org.springframework.cloud.client.discovery.DiscoveryClient:
springCloud封装的服务发现接口,springclouderueka有EurekaDiscoveryClient实现,在EurekaDiscoveryClient中服务发现交给com.netflix.discovery.EurekaClient实现
CloudEurekaClient:继承了com.netflix.discovery.DiscoveryClient,通过调用父类的构造方法初始化统计、续约、心跳检测等任务。
org.springframework.cloud.client.discovery.DiscoveryClient服务发现接口:
public interface DiscoveryClient {
/**
* A human readable description of the implementation, used in HealthIndicator
* @return the description
*/
String description();
/**
* Get all ServiceInstances associated with a particular serviceId
* @param serviceId the serviceId to query
* @return a List of ServiceInstance
*/
List getInstances(String serviceId);
/**
* @return all known service ids
*/
List getServices();
}
com.netflix.discovery.EurekaClient主要接口: 服务发现、注册、续约、心跳检测 等
public interface EurekaClient extends LookupService {
// ========================
// getters for InstanceInfo
// ========================
/**
* @param region the region that the Applications reside in
* @return an {@link com.netflix.discovery.shared.Applications} for the matching region. a Null value
* is treated as the local region.
*/
public Applications getApplicationsForARegion(@Nullable String region);
/**
* Get all applications registered with a specific eureka service.
*
* @param serviceUrl The string representation of the service url.
* @return The registry information containing all applications.
*/
public Applications getApplications(String serviceUrl);
/**
* Gets the list of instances matching the given VIP Address.
*
* @param vipAddress The VIP address to match the instances for.
* @param secure true if it is a secure vip address, false otherwise
* @return - The list of {@link InstanceInfo} objects matching the criteria
*/
public List getInstancesByVipAddress(String vipAddress, boolean secure);
/**
* Gets the list of instances matching the given VIP Address in the passed region.
*
* @param vipAddress The VIP address to match the instances for.
* @param secure true if it is a secure vip address, false otherwise
* @param region region from which the instances are to be fetched. If null
then local region is
* assumed.
*
* @return - The list of {@link InstanceInfo} objects matching the criteria, empty list if not instances found.
*/
public List getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region);
/**
* Gets the list of instances matching the given VIP Address and the given
* application name if both of them are not null. If one of them is null,
* then that criterion is completely ignored for matching instances.
*
* @param vipAddress The VIP address to match the instances for.
* @param appName The applicationName to match the instances for.
* @param secure true if it is a secure vip address, false otherwise.
* @return - The list of {@link InstanceInfo} objects matching the criteria.
*/
public List getInstancesByVipAddressAndAppName(String vipAddress, String appName, boolean secure);
// ==========================
// getters for local metadata
// ==========================
/**
* @return in String form all regions (local + remote) that can be accessed by this client
*/
public Set getAllKnownRegions();
/**
* @return the current self instance status as seen on the Eureka server.
*/
public InstanceInfo.InstanceStatus getInstanceRemoteStatus();
/**
* @deprecated see {@link com.netflix.discovery.endpoint.EndpointUtils} for replacement
*
* Get the list of all eureka service urls for the eureka client to talk to.
*
* @param zone the zone in which the client resides
* @return The list of all eureka service urls for the eureka client to talk to.
*/
@Deprecated
public List getDiscoveryServiceUrls(String zone);
/**
* @deprecated see {@link com.netflix.discovery.endpoint.EndpointUtils} for replacement
*
* Get the list of all eureka service urls from properties file for the eureka client to talk to.
*
* @param instanceZone The zone in which the client resides
* @param preferSameZone true if we have to prefer the same zone as the client, false otherwise
* @return The list of all eureka service urls for the eureka client to talk to
*/
@Deprecated
public List getServiceUrlsFromConfig(String instanceZone, boolean preferSameZone);
/**
* @deprecated see {@link com.netflix.discovery.endpoint.EndpointUtils} for replacement
*
* Get the list of all eureka service urls from DNS for the eureka client to
* talk to. The client picks up the service url from its zone and then fails over to
* other zones randomly. If there are multiple servers in the same zone, the client once
* again picks one randomly. This way the traffic will be distributed in the case of failures.
*
* @param instanceZone The zone in which the client resides.
* @param preferSameZone true if we have to prefer the same zone as the client, false otherwise.
* @return The list of all eureka service urls for the eureka client to talk to.
*/
@Deprecated
public List getServiceUrlsFromDNS(String instanceZone, boolean preferSameZone);
// ===========================
// healthcheck related methods
// ===========================
/**
* @deprecated Use {@link #registerHealthCheck(com.netflix.appinfo.HealthCheckHandler)} instead.
*
* Register {@link HealthCheckCallback} with the eureka client.
*
* Once registered, the eureka client will invoke the
* {@link HealthCheckCallback} in intervals specified by
* {@link EurekaClientConfig#getInstanceInfoReplicationIntervalSeconds()}.
*
* @param callback app specific healthcheck.
*/
@Deprecated
public void registerHealthCheckCallback(HealthCheckCallback callback);
/**
* Register {@link HealthCheckHandler} with the eureka client.
*
* Once registered, the eureka client will first make an onDemand update of the
* registering instanceInfo by calling the newly registered healthcheck handler,
* and subsequently invoke the {@link HealthCheckHandler} in intervals specified
* by {@link EurekaClientConfig#getInstanceInfoReplicationIntervalSeconds()}.
*
* @param healthCheckHandler app specific healthcheck handler.
*/
public void registerHealthCheck(HealthCheckHandler healthCheckHandler);
/**
* Register {@link EurekaEventListener} with the eureka client.
*
* Once registered, the eureka client will invoke {@link EurekaEventListener#onEvent}
* whenever there is a change in eureka client's internal state. Use this instead of
* polling the client for changes.
*
* {@link EurekaEventListener#onEvent} is called from the context of an internal thread
* and must therefore return as quickly as possible without blocking.
*
* @param eventListener
*/
public void registerEventListener(EurekaEventListener eventListener);
/**
* Unregister a {@link EurekaEventListener} previous registered with {@link EurekaClient#registerEventListener}
* or injected into the constructor of {@link DiscoveryClient}
*
* @param eventListener
* @return True if removed otherwise false if the listener was never registered.
*/
public boolean unregisterEventListener(EurekaEventListener eventListener);
/**
* @return the current registered healthcheck handler
*/
public HealthCheckHandler getHealthCheckHandler();
// =============
// other methods
// =============
/**
* Shuts down Eureka Client. Also sends a deregistration request to the eureka server.
*/
public void shutdown();
/**
* @return the configuration of this eureka client
*/
public EurekaClientConfig getEurekaClientConfig();
/**
* @return the application info manager of this eureka client
*/
public ApplicationInfoManager getApplicationInfoManager();
}
com.netflix.discovery.DiscoveryClient部分源码
初始化定时任务
private void initScheduledTasks() {
//定时拉取信息列表
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
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
scheduler.schedule(
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
//事件监听器,实例变化时发起change事件
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()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
//服务续约 rest请求
boolean renew() {
EurekaHttpResponse httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
//注册 rest请求
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
//通知注册中心失效
void unregister() {
// It can be null if shouldRegisterWithEureka == false
if(eurekaTransport != null && eurekaTransport.registrationClient != null) {
try {
logger.info("Unregistering ...");
EurekaHttpResponse httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId());
logger.info(PREFIX + "{} - deregister status: {}", appPathIdentifier, httpResponse.getStatusCode());
} catch (Exception e) {
logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e);
}
}
}
EurekaServiceRegistry:eureka注册类实现了SpringCloud定义的服务注册接口org.springframework.cloud.client.serviceregistry.ServiceRegistry,源码中最终也是交给com.netflix.discovery.DiscoveryClient实现
EurekaRegistration:封装注册信息