Spring Cloud 四:Eureka Client源码详细分析

一,Eureka Client概述

1,Eureka Client工作流程图

在这里插入图片描述

2,@EnableDiscoveryClient注解

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaClientApplication.class, args);
    }

}

注意:Eureka Client在1版本是必须要加@EnableDiscoveryClient注解的,2版本@EnableDiscoveryClient注解加不加都没有影响。

3,EurekaClientConfig和EurekaInstanceConfig

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({
    
     NoopDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
    
    
		"org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
		"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
		"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
		"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    
    
    
    //省略部分代码
    
	@Bean
	@ConditionalOnMissingBean(value = EurekaClientConfig.class,
			search = SearchStrategy.CURRENT)
	public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
    
    
		return new EurekaClientConfigBean();
	}
    
    @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.servlet.context-path", "/");
		int serverPort = Integer.parseInt(
				env.getProperty("server.port", env.getProperty("port", "8080")));

		Integer managementPort = env.getProperty("management.server.port", Integer.class);
		String managementContextPath = env
				.getProperty("management.server.servlet.context-path");
		Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port",
				Integer.class);
		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<String, String> metadataMap = instance.getMetadataMap();
			metadataMap.computeIfAbsent("management.port",
					k -> String.valueOf(metadata.getManagementPort()));
		}
		else {
    
    
			// without the metadata the status and health check URLs will not be set
			// and the status page and health check url paths will not include the
			// context path so set them here
			if (StringUtils.hasText(managementContextPath)) {
    
    
				instance.setHealthCheckUrlPath(
						managementContextPath + instance.getHealthCheckUrlPath());
				instance.setStatusPageUrlPath(
						managementContextPath + instance.getStatusPageUrlPath());
			}
		}

		setupJmxPort(instance, jmxPort);
		return instance;
	}
    
    //省略部分代码
}

EurekaClientConfig

EurekaClientConfig是个接口,其实现类是EurekaClientConfigBean,此类封装了以"eureka.client"开头的配置信息如:application.yaml

eureka:
  client:
    # Eureka服务器的地址,类型为HashMap,缺省的Key为 defaultZone;缺省的Value为 http://localhost:8761/eureka
    # 如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
    service-url:
      defaultZone: http://euk-server1:7001/eureka/
    # 是否向注册中心注册自己,缺省:true
    register-with-eureka: true
    # 是否从Eureka获取注册信息,缺省:true
    fetch-registry: true
    # 客户端拉取服务注册信息间隔,单位:秒,缺省:30
    registry-fetch-interval-seconds: 30
    # 是否启用客户端健康检查
    healthcheck:
      enabled: true
    # 连接Eureka服务器的超时时间,单位:秒,缺省:5
    eureka-server-connect-timeout-seconds: 5
    # 从Eureka服务器读取信息的超时时间,单位:秒,缺省:8
    eureka-server-read-timeout-seconds: 8
    # 获取实例时是否只保留状态为 UP 的实例,缺省:true
    filter-only-up-instances: true
    # Eureka服务端连接空闲时的关闭时间,单位:秒,缺省:30
    eureka-connection-idle-timeout-seconds: 30
    # 从Eureka客户端到所有Eureka服务端的连接总数,缺省:200
    eureka-server-total-connections: 200
    # 从Eureka客户端到每个Eureka服务主机的连接总数,缺省:50
    eureka-server-total-connections-per-host: 50

EurekaInstanceConfig

EurekaInstanceConfig是个接口,其实现类是EurekaInstanceConfigBean,此类封装了以"eureka.instance"开头的配置信息,主要用于构建InstanceInfo。如:application.yaml

eureka:
  instance:
    # 应用实例主机名
    hostname: euk-client1
    # 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
    appname: EurekaClient
    # 实例ID
    instance-id: EurekaClientInstance1
    # 服务失效时间,失效的服务将被剔除。单位:秒,默认:90
    lease-expiration-duration-in-seconds: 90
    # 服务续约(心跳)频率,单位:秒,缺省30
    lease-renewal-interval-in-seconds: 30

4,InstanceInfo

InstanceInfo封装了将被发送到Eureka Server进行注册的服务实例元数据。它在Eureka Server列表中代表一个服务实例,其他服务可以通过instanceInfo了解到该服务的实例相关信息,包括地址等,从而发起请求。

public class InstanceInfo {
    
    
    
    //省略部分代码
    
    public InstanceInfo(
            @JsonProperty("instanceId") String instanceId,
            @JsonProperty("app") String appName,
            @JsonProperty("appGroupName") String appGroupName,
            @JsonProperty("ipAddr") String ipAddr,
            @JsonProperty("sid") String sid,
            @JsonProperty("port") PortWrapper port,
            @JsonProperty("securePort") PortWrapper securePort,
            @JsonProperty("homePageUrl") String homePageUrl,
            @JsonProperty("statusPageUrl") String statusPageUrl,
            @JsonProperty("healthCheckUrl") String healthCheckUrl,
            @JsonProperty("secureHealthCheckUrl") String secureHealthCheckUrl,
            @JsonProperty("vipAddress") String vipAddress,
            @JsonProperty("secureVipAddress") String secureVipAddress,
            @JsonProperty("countryId") int countryId,
            @JsonProperty("dataCenterInfo") DataCenterInfo dataCenterInfo,
            @JsonProperty("hostName") String hostName,
            @JsonProperty("status") InstanceStatus status,
            @JsonProperty("overriddenstatus") InstanceStatus overriddenStatus,
            @JsonProperty("overriddenStatus") InstanceStatus overriddenStatusAlt,
            @JsonProperty("leaseInfo") LeaseInfo leaseInfo,
            @JsonProperty("isCoordinatingDiscoveryServer") Boolean isCoordinatingDiscoveryServer,
            @JsonProperty("metadata") HashMap<String, String> metadata,
            @JsonProperty("lastUpdatedTimestamp") Long lastUpdatedTimestamp,
            @JsonProperty("lastDirtyTimestamp") Long lastDirtyTimestamp,
            @JsonProperty("actionType") ActionType actionType,
            @JsonProperty("asgName") String asgName) {
    
    
        this.instanceId = instanceId;
        this.sid = sid;
        this.appName = StringCache.intern(appName);
        this.appGroupName = StringCache.intern(appGroupName);
        this.ipAddr = ipAddr;
        this.port = port == null ? 0 : port.getPort();
        this.isUnsecurePortEnabled = port != null && port.isEnabled();
        this.securePort = securePort == null ? 0 : securePort.getPort();
        this.isSecurePortEnabled = securePort != null && securePort.isEnabled();
        this.homePageUrl = homePageUrl;
        this.statusPageUrl = statusPageUrl;
        this.healthCheckUrl = healthCheckUrl;
        this.secureHealthCheckUrl = secureHealthCheckUrl;
        this.vipAddress = StringCache.intern(vipAddress);
        this.secureVipAddress = StringCache.intern(secureVipAddress);
        this.countryId = countryId;
        this.dataCenterInfo = dataCenterInfo;
        this.hostName = hostName;
        this.status = status;
        this.overriddenStatus = overriddenStatus == null ? overriddenStatusAlt : overriddenStatus;
        this.leaseInfo = leaseInfo;
        this.isCoordinatingDiscoveryServer = isCoordinatingDiscoveryServer;
        this.lastUpdatedTimestamp = lastUpdatedTimestamp;
        this.lastDirtyTimestamp = lastDirtyTimestamp;
        this.actionType = actionType;
        this.asgName = StringCache.intern(asgName);

        // ---------------------------------------------------------------
        // for compatibility

        if (metadata == null) {
    
    
            this.metadata = Collections.emptyMap();
        } else if (metadata.size() == 1) {
    
    
            this.metadata = removeMetadataMapLegacyValues(metadata);
        } else {
    
    
            this.metadata = metadata;
        }

        if (sid == null) {
    
    
            this.sid = SID_DEFAULT;
        }
    }
    
    //省略部分代码
}

ApplicationInfoManager中有一个方法:

public class ApplicationInfoManager {
    
    
    
    //省略部分代码
    
	public void initComponent(EurekaInstanceConfig config) {
    
    
        try {
    
    
            this.config = config;
            this.instanceInfo = new EurekaConfigBasedInstanceInfoProvider(config).get();
        } catch (Throwable e) {
    
    
            throw new RuntimeException("Failed to initialize ApplicationInfoManager", e);
        }
    }
    
    //省略部分代码
}

通过EurekaInstanceConfig构造instanceInfo

5,服务发现

DiscoveryClient是在spring cloud中定义的,用于服务发现的顶级接口:

在这里插入图片描述

package org.springframework.cloud.client.discovery;
public interface DiscoveryClient extends Ordered {
    
    

    // 获取实现类的描述。
	String description();

	// 通过服务id查询服务实例信息列表。
	List<ServiceInstance> getInstances(String serviceId);

	// 获取所有服务实例id
	List<String> getServices();

}

spring-cloud-commons-2.2.1.realease包下,org.springframework.cloud.client.discovery.DiscoveryClient接口。定义用来服务发现的客户端接口,是客户端进行服务发现的核心接口,在common中可以看到其地位。在Netflix Eureka和Consul中都有具体的实现类。

我们来看Spring Cloud中Eureka的实现:

package org.springframework.cloud.netflix.eureka;
public class EurekaDiscoveryClient implements DiscoveryClient {
    
    

	/**
	 * Client description {@link String}.
	 */
	public static final String DESCRIPTION = "Spring Cloud Eureka Discovery Client";

	private final EurekaClient eurekaClient;

	private final EurekaClientConfig clientConfig;

	@Deprecated
	public EurekaDiscoveryClient(EurekaInstanceConfig config, EurekaClient eurekaClient) {
    
    
		this(eurekaClient, eurekaClient.getEurekaClientConfig());
	}

	public EurekaDiscoveryClient(EurekaClient eurekaClient,
			EurekaClientConfig clientConfig) {
    
    
		this.clientConfig = clientConfig;
		this.eurekaClient = eurekaClient;
	}

	@Override
	public String description() {
    
    
		return DESCRIPTION;
	}

	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
    
    
		List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId,
				false);
		List<ServiceInstance> instances = new ArrayList<>();
		for (InstanceInfo info : infos) {
    
    
			instances.add(new EurekaServiceInstance(info));
		}
		return instances;
	}

	@Override
	public List<String> getServices() {
    
    
		Applications applications = this.eurekaClient.getApplications();
		if (applications == null) {
    
    
			return Collections.emptyList();
		}
		List<Application> registered = applications.getRegisteredApplications();
		List<String> names = new ArrayList<>();
		for (Application app : registered) {
    
    
			if (app.getInstances().isEmpty()) {
    
    
				continue;
			}
			names.add(app.getName().toLowerCase());

		}
		return names;
	}

	@Override
	public int getOrder() {
    
    
		return clientConfig instanceof Ordered ? ((Ordered) clientConfig).getOrder()
				: DiscoveryClient.DEFAULT_ORDER;
	}

	/**
	 * An Eureka-specific {@link ServiceInstance} implementation. Extends
	 * {@link org.springframework.cloud.netflix.eureka.EurekaServiceInstance} for
	 * backwards compatibility.
	 *
	 * @deprecated In favor of
	 * {@link org.springframework.cloud.netflix.eureka.EurekaServiceInstance}.
	 */
	@Deprecated
	public static class EurekaServiceInstance
			extends org.springframework.cloud.netflix.eureka.EurekaServiceInstance {
    
    

		public EurekaServiceInstance(InstanceInfo instance) {
    
    
			super(instance);
		}

	}

}

EurekaDiscoveryClient类实现了DiscoveryClient接口,类的逻辑是通过组合了com.netflix.discovery.EurekaClient来实现。也就是说spring cloud中通过组合了netflixeureka-client包来实现服务发现的。

下面我们来走进netflix的eureka-client包下看EurekaClient接口以及它的实现类DiscoveryClient

package com.netflix.discovery;
public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    // 服务注册
    boolean register() throws Throwable {
    
    
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> 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() == Status.NO_CONTENT.getStatusCode();
    }

    // 服务续约
    boolean renew() {
    
    
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
    
    
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
    
    
                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() == Status.OK.getStatusCode();
        } catch (Throwable e) {
    
    
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }
    
    // 服务下线
    @PreDestroy
    @Override
    public synchronized void shutdown() {
    
    
        if (isShutdown.compareAndSet(false, true)) {
    
    
            logger.info("Shutting down DiscoveryClient ...");

            if (statusChangeListener != null && applicationInfoManager != null) {
    
    
                applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }

            cancelScheduledTasks();

            // If APPINFO was registered
            if (applicationInfoManager != null
                    && clientConfig.shouldRegisterWithEureka()
                    && clientConfig.shouldUnregisterOnShutdown()) {
    
    
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                unregister();
            }

            if (eurekaTransport != null) {
    
    
                eurekaTransport.shutdown();
            }

            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();

            Monitors.unregisterObject(this);

            logger.info("Completed shut down of DiscoveryClient");
        }
    }
    
    // 省略部分代码
}

EurekaClient有一个注解@ImplementedBy(DiscoveryClient.class),此类的默认实现类:com.netflix.discovery.DiscoveryClient。提供了:

  • 服务注册到server方法register()。
  • 续约boolean renew()。
  • 下线public synchronized void shutdown()。
  • 查询服务列表 等功能。

此类DiscoveryClient正是上面spring cloud包下EurekaDiscoveryClient类成员变量EurekaClient接口的具体实现。

EurekaClient继承了LookupService

LookupService作用:发现活跃的服务实例。

package com.netflix.discovery.shared;
public interface LookupService<T> {
    
    

    // 根据服务实例注册的appName来获取封装有相同appName的服务实例信息容器
    Application getApplication(String appName);

    // 获取所有的服务实例信息
    Applications getApplications();

    //根据实例id,获取服务实例信息
    List<InstanceInfo> getInstancesById(String id);
}
  • 上面提到一个Application,它持有服务实例信息列表。它是同一个服务的集群信息。比如EurekaClient1的所有服务信息,这些服务都在EurekaClient1服务名下面。
  • Applications是注册表中,所有服务实例信息的集合。
  • InstanceInfo代表一个服务实例的信息。

二,Eureka Client启动

我们回到启动类EurekaClientAutoConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({
    
     NoopDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
    
    
		"org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
		"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
		"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
		"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    
    
	@Configuration(proxyBeanMethods = false)
	@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);
		}
        
        // 省略部分代码
    }
    
    //省略部分代码
}

向spring容器注入了CloudEurekaClient,我们看其构造函数:

public class CloudEurekaClient extends DiscoveryClient {
    
    
    public CloudEurekaClient(ApplicationInfoManager applicationInfoManager,
			EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args,
			ApplicationEventPublisher publisher) {
    
    
        // 调用父类构造函数
		super(applicationInfoManager, config, args);
		this.applicationInfoManager = applicationInfoManager;
		this.publisher = publisher;
		this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class,
				"eurekaTransport");
		ReflectionUtils.makeAccessible(this.eurekaTransportField);
	}
}

看其父类DiscoveryClient构造函数:

public class DiscoveryClient implements EurekaClient {
    
    

	// 省略部分代码

	@Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
    
    
        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(","));

        // shouldFetchRegistry,点其实现类EurekaClientConfigBean,找到它其实对应于:
        // eureka.client.fetch-register,true:表示client从server拉取注册表信息。
        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());

        // 如果以下两个都为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());

            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 {
    
    
            /**
             * 两个定时任务
             */
            // 定义了一个基于线程池的定时器线程池,大小为2。
            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交互的Jersey客户端
             */
            
            // 它是eureka Client和eureka server进行http交互jersey客户端。
            // 点开EurekaTransport,看到许多httpclient相关的属性。
            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);
        }
		
        /**
         * 拉取注册信息
         */
        // eureka.client.fetch-register,true:表示client从server拉取注册表信息。
        if (clientConfig.shouldFetchRegistry()) {
    
    
            try {
    
    
                // 从eureka server拉取注册表中的信息。
                boolean primaryFetchRegistryResult = fetchRegistry(false);
                if (!primaryFetchRegistryResult) {
    
    
                    logger.info("Initial registry fetch from primary servers failed");
                }
                boolean backupFetchRegistryResult = true;
                // fetchRegistryFromBackup():将注册表缓存到本地,可以就近获取其他服务信息,减少于server的交互。
                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);
            }
        }

        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);
            }
        }

        /**
         * 启动定时任务
         */
        // 此方法中,启动3个定时任务。方法内有statusChangeListener,
        // 按需注册是一个事件StatusChangeEvent,状态改变,则向server注册。
        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);
    }
    
    // 省略部分代码
}

此函数中依次执行了 从eureka server中拉取注册表,服务注册,初始化发送心跳,缓存刷新(定时拉取注册表信息),按需注册定时任务等,贯穿了Eureka Client启动阶段的各项工作。

总结DiscoveryClient构造关键过程:

  1. 初始化一堆信息。
  2. 拉取注册表信息。
  3. 向server注册自己。
  4. 初始化3个任务。

下面我们来详细看一下这几个过程:

1,拉取注册表信息

上面的fetchRegistry(false),点进去:

public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    //拉取服务信息
    private boolean fetchRegistry(boolean forceFullRegistryFetch) {
    
    
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
    
    
            Applications applications = getApplications();
            // 如果增量式拉取被禁止或第一次拉取注册表,则进行全量拉取
            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
    
    
                // 全量拉去
                getAndStoreFullRegistry();
            } else {
    
    
                // 否则进行增量拉取注册表信息
                getAndUpdateDelta(applications);
            }
           
        } catch (Throwable e) {
    
    
            return false;
        } finally {
    
    
            if (tracer != null) {
    
    
                tracer.stop();
            }
        }

        // 在更新实例远程状态之前通知有关缓存刷新的信息
        onCacheRefreshed();
        // 根据缓存中保存的刷新数据更新远程状态
        updateInstanceRemoteStatus();
        // 拉取成功,返回true
        return true;
    }
    
    // 全量拉取
    private void getAndStoreFullRegistry() throws Throwable {
    
    
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        logger.info("Getting all instance registry info from the eureka server");

        Applications apps = null;
        EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
                ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
                : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
    
    
            apps = httpResponse.getEntity();
        }
        logger.info("The response status is {}", httpResponse.getStatusCode());

        if (apps == null) {
    
    
            logger.error("The application is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
    
    
            localRegionApps.set(this.filterAndShuffle(apps));
            logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
        } else {
    
    
            logger.warn("Not updating applications as another thread is updating it already");
        }
    }
}

1,全量拉取

	// 全量拉取
    private void getAndStoreFullRegistry() throws Throwable {
    
    
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        logger.info("Getting all instance registry info from the eureka server");

        Applications apps = null;
        EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null
                ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get())
                : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
    
    
            apps = httpResponse.getEntity();
        }
        logger.info("The response status is {}", httpResponse.getStatusCode());

        if (apps == null) {
    
    
            logger.error("The application is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
    
    
            localRegionApps.set(this.filterAndShuffle(apps));
            logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode());
        } else {
    
    
            logger.warn("Not updating applications as another thread is updating it already");
        }
    }

有一方法:eurekaTransport.queryClient.getApplications。通过debug发现 实现类是AbstractJerseyEurekaHttpClient,点开,debug出 webResource地址为:http://root:root@eureka-7900:7900/eureka/apps/,此端点用于获取server中所有的注册表信息。

getAndStoreFullRegistry()可能被多个线程同时调用,导致新拉取的注册表被旧的覆盖(如果新拉取的动作设置apps阻塞的情况下)。

此时用了AutomicLong来进行版本管理,如果更新时版本不一致,不保存apps。

通过这个判断fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1),如果版本一致,并设置新版本(+1),接着执行localRegionApps.set(this.filterAndShuffle(apps));过滤并洗牌apps。

点开this.filterAndShuffle(apps)实现,继续点apps.shuffleAndIndexInstances,继续点shuffleInstances,继续点application.shuffleAndStoreInstances,继续点_shuffleAndStoreInstances,发现if (filterUpInstances && InstanceStatus.UP != instanceInfo.getStatus())。只保留状态为UP的服务。

2,增量拉取

private void getAndUpdateDelta(Applications applications) throws Throwable {
    
    
        long currentUpdateGeneration = fetchRegistryGeneration.get();

        Applications delta = null;
        EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
        if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
    
    
            delta = httpResponse.getEntity();
        }

        if (delta == null) {
    
    
            logger.warn("The server does not allow the delta revision to be applied because it is not safe. "
                    + "Hence got the full registry.");
            getAndStoreFullRegistry();
        } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) {
    
    
            logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode());
            String reconcileHashCode = "";
            if (fetchRegistryUpdateLock.tryLock()) {
    
    
                try {
    
    
                    updateDelta(delta);
                    reconcileHashCode = getReconcileHashCode(applications);
                } finally {
    
    
                    fetchRegistryUpdateLock.unlock();
                }
            } else {
    
    
                logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
            }
            // There is a diff in number of instances for some reason
            if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) {
    
    
                reconcileAndLogDifference(delta, reconcileHashCode);  // this makes a remoteCall
            }
        } else {
    
    
            logger.warn("Not updating application delta as another thread is updating it already");
            logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode());
        }
    }
  • 通过getDelta方法,看到实际拉取的地址是:apps/delta,如果获取到的delta为空,则全量拉取。
  • 通常来讲是3分钟之内注册表的信息变化(在server端判断),获取到delta后,会更新本地注册表。
  • 增量式拉取是为了维护client和server端 注册表的一致性,防止本地数据过久,而失效,采用增量式拉取的方式,减少了client和server的通信量。
  • client有一个注册表缓存刷新定时器,专门负责维护两者之间的信息同步,但是当增量出现意外时,定时器将执行,全量拉取以更新本地缓存信息。

更新本地注册表方法updateDelta,有一个细节。

 if (ActionType.ADDED.equals(instance.getActionType()))public enum ActionType {
    
    
          ADDED, // Added in the discovery server
          MODIFIED, // Changed in the discovery server
          DELETED
          // Deleted from the discovery server
      }

在InstanceInfo instance中有一个instance.getActionType()ADDEDMODIFIED状态的将更新本地注册表applications.addApplicationDELETED将从本地剔除掉existingApp.removeInstance(instance)

2,服务注册

好了拉取完eureka server中的注册表了,接着进行服务注册。回到DiscoveryClient构造函数。

拉取fetchRegistry完后进行register注册。由于构造函数开始时已经将服务实例元数据封装好了instanceInfo,所以此处之间向server发送instanceInfo:

public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    //服务注册
    boolean register() throws Throwable {
    
    
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> 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());
        }
        // 204状态码,则注册成功。
        return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
    }
    
    // 省略部分代码
}

3,初始化3个定时任务

initScheduledTasks()方法初始化3个定时任务。

  1. client会定时向server发送心跳,维持自己服务租约的有效性,用心跳定时任务实现;
  2. 而server中会有不同的服务实例注册进来,一进一出,就需要数据的同步。所以client需要定时从server拉取注册表信息,用缓存定时任务实现;
  3. client如果有变化,也会及时更新server中自己的信息,用按需注册定时任务实现。

没错就是这三个定时任务,进 initScheduledTasks()方法中:

public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    //初始化心跳定时任务,缓存定时任务,按需注册定时任务
    private void initScheduledTasks() {
    
    
        //从server拉取注册表信息。
        if (clientConfig.shouldFetchRegistry()) {
    
    
            // 拉取的时间间隔,通过eureka.client.registry-fetch-interval-seconds进行设置。
            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()) {
    
    
            // 心跳定时器,默认30秒。
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // 心跳定时任务
            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            );
            scheduler.schedule(
                    heartbeatTask,
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // 按需注册定时任务实现
            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()) {
    
    
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
    
    
            logger.info("Not registering with Eureka server per configuration");
        }
    }
    // 省略部分代码
}

1,缓存刷新定时任务

public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    class CacheRefreshThread implements Runnable {
    
    
        public void run() {
    
    
            refreshRegistry();
        }
    }
    
    void refreshRegistry() {
    
    
        
        // 省略部分代码
        
        boolean success = fetchRegistry(remoteRegionsModified);
            
        // 省略部分代码
    }
    
    /**
     *	最后回到了我们上面讲过的拉取注册表信息的方法
     */
    //拉取服务信息
    private boolean fetchRegistry(boolean forceFullRegistryFetch) {
    
    
        Stopwatch tracer = FETCH_REGISTRY_TIMER.start();

        try {
    
    
            Applications applications = getApplications();
            // 如果增量式拉取被禁止或第一次拉取注册表,则进行全量拉取
            if (clientConfig.shouldDisableDelta()
                    || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
                    || forceFullRegistryFetch
                    || (applications == null)
                    || (applications.getRegisteredApplications().size() == 0)
                    || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
            {
    
    
                // 全量拉去
                getAndStoreFullRegistry();
            } else {
    
    
                // 否则进行增量拉取注册表信息
                getAndUpdateDelta(applications);
            }
           
        } catch (Throwable e) {
    
    
            return false;
        } finally {
    
    
            if (tracer != null) {
    
    
                tracer.stop();
            }
        }

        // 在更新实例远程状态之前通知有关缓存刷新的信息
        onCacheRefreshed();
        // 根据缓存中保存的刷新数据更新远程状态
        updateInstanceRemoteStatus();
        // 拉取成功,返回true
        return true;
    }
    
    // 省略部分代码
}

2,心跳定时任务

public class DiscoveryClient implements EurekaClient {
    
    
	
	// 省略部分代码
	
	private class HeartbeatThread implements Runnable {
    
    

        public void run() {
    
    
            if (renew()) {
    
    
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }
    
    boolean renew() {
    
    
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
    
    
            httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            // 看到如果遇到404,server没有此实例,则重新发起注册。如果续约成功返回 200.
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
    
    
                REREGISTER_COUNTER.increment();
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
    
    
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == Status.OK.getStatusCode();
        } catch (Throwable e) {
    
    
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }
	//省略部分代码
}

3,按需注册定时任务

instanceinfostatus发生变化时,需要向server同步,去更新自己在server中的实例信息。保证server注册表中服务实例信息的有效和可用。

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()) {
    
    
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());

此定时任务有2个部分:

1,定时刷新服务实例信息和检查应用状态的变化,在服务实例信息发生改变的情况下向server重新发起注册。InstanceInfoReplicator点进去。看到一个方法 :
class InstanceInfoReplicator implements Runnable {
    
    
    
    // 省略部分代码
    
    public void run() {
    
    
        try {
    
    
            // 刷新instanceinfo,如果有变化,在下次心跳时,同步向server。
            discoveryClient.refreshInstanceInfo();
			// 如果实例信息有变,返回数据更新时间
            Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
            if (dirtyTimestamp != null) {
    
    
                discoveryClient.register();
                instanceInfo.unsetIsDirty(dirtyTimestamp);
            }
        } catch (Throwable t) {
    
    
            logger.warn("There was a problem with the instance info replicator", t);
        } finally {
    
    
            // 延时执行下一个检查任务。用于再次调用run方法,继续检查服务实例信息和状态的变化
            Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
            scheduledPeriodicRef.set(next);
        }
    }
    
    // 省略部分代码
}
2,注册状态改变监听器,在应用状态发生变化时,刷新服务实例信息,在服务实例信息发生改变时向server注册。
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);
                    }
                    如果状态发生改变,调用onDemandUpdate(),点onDemandUpdate进去,看到InstanceInfoReplicator.this.run();     
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

总结:两部分,一部分自己去检查,一部分等待状态监听事件。

三,服务下线

服务下线:在应用关闭时,client会向server注销自己,在Discoveryclient销毁前,会执行下面清理方法。

public class DiscoveryClient implements EurekaClient {
    
    
    
    // 省略部分代码
    
    @PreDestroy
    @Override
    public synchronized void shutdown() {
    
    
        if (isShutdown.compareAndSet(false, true)) {
    
    
            logger.info("Shutting down DiscoveryClient ...");

            if (statusChangeListener != null && applicationInfoManager != null) {
    
    
    
 // 注销监听器
 applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId());
            }
			// 取消定时任务
            cancelScheduledTasks();

            // If APPINFO was registered
            if (applicationInfoManager != null
                    && clientConfig.shouldRegisterWithEureka()
                    && clientConfig.shouldUnregisterOnShutdown()) {
    
    
                applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN);
                // 服务下线
                unregister();
            }
			// 关闭jersy客户端
            if (eurekaTransport != null) {
    
    
                eurekaTransport.shutdown();
            }

            heartbeatStalenessMonitor.shutdown();
            registryStalenessMonitor.shutdown();

            Monitors.unregisterObject(this);

            logger.info("Completed shut down of DiscoveryClient");
        }
    }
    
    // 省略部分代码
}

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/109679422