SpringCloud eureka客户端启动过程解析

版本:
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,通过调用父类的构造方法初始化统计、续约、心跳检测等任务。
SpringCloud eureka客户端启动过程解析_第1张图片
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:封装注册信息

SpringCloud eureka客户端启动过程解析_第2张图片

你可能感兴趣的:(微服务,eureka)